56

I load an external library that is placed in ./lib. Are these two solutions to set the java.library.path equivalent?

  1. Set path in console when executing jar:

    java -Djava.library.path=./lib -jar myApplication.jar
    
  2. Set path in the code before loading library:

    System.setProperty("java.library.path", "./lib");
    

If they are equivalent, why in the second solution can Java not find the library while the first one is ok?

If not, is there a way the set the path in the code?

2
  • java.library.path refers to directory not a file Commented Apr 2, 2015 at 18:15
  • 1
    As of Java 13, the API doc says --- "Property values may be cached during initialization or on first use. Setting a standard property after initialization using getProperties(), setProperties(Properties), setProperty(String, String), or clearProperty(String) may not have the desired effect". Commented Nov 5, 2019 at 12:12

5 Answers 5

60

Although it is not well documented, the java.library.path system property is a "read-only" property as far as the System.loadLibrary() method is concerned. This is a reported bug but it was closed by Sun as opposed to getting fixed. The problem is that the JVM's ClassLoader reads this property once at startup and then caches it, not allowing us to change it programatically afterward. The line System.setProperty("java.library.path", anyVal); will have no effect except for System.getProperty() method calls.

Luckily, someone posted a workaround on the Sun forums. Unfortunately, that link no longer works but I did find the code on another source. Here is the code you can use to work around not being able to set the java.library.path system property:

public static void addDir(String s) throws IOException {
    try {
        // This enables the java.library.path to be modified at runtime
        // From a Sun engineer at http://forums.sun.com/thread.jspa?threadID=707176
        //
        Field field = ClassLoader.class.getDeclaredField("usr_paths");
        field.setAccessible(true);
        String[] paths = (String[])field.get(null);
        for (int i = 0; i < paths.length; i++) {
            if (s.equals(paths[i])) {
                return;
            }
        }
        String[] tmp = new String[paths.length+1];
        System.arraycopy(paths,0,tmp,0,paths.length);
        tmp[paths.length] = s;
        field.set(null,tmp);
        System.setProperty("java.library.path", System.getProperty("java.library.path") + File.pathSeparator + s);
    } catch (IllegalAccessException e) {
        throw new IOException("Failed to get permissions to set library path");
    } catch (NoSuchFieldException e) {
        throw new IOException("Failed to get field handle to set library path");
    }
}

WARNING: This may not work on all platforms and/or JVMs.

Sign up to request clarification or add additional context in comments.

3 Comments

Check out this newer answer by @luyifan which appears to accomplish the same thing as this answer, with less code.
The newer answer by @luyifan is not working on Java 11 due to below error. java.lang.NoClassDefFoundError: Could not initialize class java.util.zip.CRC32 But your code works without any error.
This answer really saved me. I have posted an addendum below for Java 17: stackoverflow.com/a/70126075/257299
55

Generally speaking, both approaches have the same net effect in that the system property java.library.path is set to the value ./lib.

However, some system properties are only evaluated at specific points in time, such as the startup of the JVM. If java.library.path is among those properties (and your experiment seems to indicate that), then using the second approach will have no noticeable effect except for returning the new value on future invocations of getProperty().

As a rule of thumb, using the -D command line property works on all system properties, while System.setProperty() only works on properties that are not only checked during startup.

Comments

46

you can add three lines

 System.setProperty("java.library.path", "/path/to/libs" );
 Field fieldSysPath = ClassLoader.class.getDeclaredField( "sys_paths" );
 fieldSysPath.setAccessible( true );
 fieldSysPath.set( null, null );

and also import java.lang.reflect.Field It's ok to solve the problem

5 Comments

this works well (and is simpler than building the temp path ourselves as described by Jesse Webb's solution)
This one is a lifesaver. Seems sloppy by Sun/Oracle to not provide a more direct way of loading libraries from a specific, runtime-defined directory.
Sad is that it's forbidden in Java 10 and will be removed ("illegal reflective access operation has occurred").
It should be noted that this is not portable across JVMs. Specifically it doesn't work for the IBM J9 JVM. I have had success in Oracle and Adopt OpenJDK though.
@luyifan How to deal with multiple libs path? Like "/path/to/lib1", "/path/to/lib2".
2

This is an addendum to this answer to Jesse Webb's amazing answer above: https://stackoverflow.com/a/6408467/257299

For Java 17:

import jdk.internal.loader.NativeLibraries;
final Class<?>[] declClassArr = NativeLibraries.class.getDeclaredClasses();
final Class<?> libraryPaths =
    Arrays.stream(declClassArr)
        .filter(klass -> klass.getSimpleName().equals("LibraryPaths"))
        .findFirst()
        .get();
final Field field = libraryPaths.getDeclaredField("USER_PATHS");
final MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(Field.class, MethodHandles.lookup());
final VarHandle varHandle = lookup.findVarHandle(Field.class, "modifiers", int.class);
varHandle.set(field, field.getModifiers() & ~Modifier.FINAL);

Since package jdk.internal.loader from module java.base is not normally accessible, you will need to add "exports" and "opens" to both the compiler and JVM runtime args.

--add-exports=java.base/jdk.internal.loader=ALL-UNNAMED
--add-opens=java.base/jdk.internal.loader=ALL-UNNAMED
--add-opens=java.base/java.lang.reflect=ALL-UNNAMED

Read more here:

1 Comment

For me, this seemed not to work any longer under Java21. I got an "UnsupportedOperationException" when trying to execute the last line of your code snippet.
1

In the Temurin JDK setting sys_paths to null doesn't work. It leads to a NullPointerException.

The following works in Temurin:

Method initLibraryPaths = ClassLoader.class.getDeclaredMethod("initLibraryPaths");
initLibraryPaths.setAccessible(true);
initLibraryPaths.invoke(null);

1 Comment

The reason isn't because of the version, it's because the JVM implementation is different. The way you introduced seems to be the implementation of Temurin JDK.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.