25

In a Java project, I am using a third-party library that loads some native library via

System.loadLibrary("libName");

I'd like to be able to influence the search path of this method from within my application, so that the user doesn't need to specify a correct java.library.path value on the command line (this value depends on the current OS and architecture). E.g on Windows I want to set it to "lib/native/windows", on Linux 32bit to "lib/native/linux32" etc.

I tried

System.setProperty("java.library.path", ...)

but this is ignored, apparently because the JVM reads this property only once before my code is run.

I also tried to load the native libray before using the Java library that depends on it with

System.load("fullPath/lib")

This call succeeds, but there will still be an UnsatisfiedLinkError when the native library is loaded again with System.loadLibrary().

The only way I found is the following:

  • Add interfaces that abstract the whole API of the external library.
  • Use only these interfaces in the rest of the code.
  • Add classes that implement the interfaces and delegate to the library.
  • Write an own ClassLoader, that
    • overwrites findLibary() so that the native library is found in the correct path
    • overwrites loadClass() and loads all classes of the external library and the wrapper layer by itself instead of trying to delegate to its parent like the default ClassLoader would do
  • Ensure that the interfaces are loaded with the normal ClassLoader and the wrapping classes and the external library are loaded with my own ClassLoader.

This works, but I find it very complicated and it is much effort because I need to add all those interfaces. Is there a simpler way?

Philipp Wendler
  • 11,184
  • 7
  • 52
  • 87
  • Did or did it not work with `System.load("fullPath/lib/native/windows/libName.dll")`? And why are you reloading it with `System.loadLibrary(..)`? – dacwe Feb 16 '11 at 07:44
  • @dacwe Yes, it did work. But the third-party library tries to load it again with System.loadLibrary(..), and this fails. – Philipp Wendler Feb 16 '11 at 08:00

6 Answers6

5

I needed to change the dll path for my unit tests. I tried the following hack and it worked:

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

For explanation, see the original link.

Samil
  • 958
  • 1
  • 17
  • 20
  • 1
    Probably the most pragmatic way for unit tests, but I would not like to use this in production code. – Philipp Wendler Jun 17 '14 at 14:27
  • Phillipp, of course it works inside of IDEs, too. For that 'runtime configurations' got invented. Just create a configuration, and set the environment variables correctly, put it under version control, so the others in the project gets it, too. – Angel O'Sphere Sep 21 '15 at 20:07
  • 1
    This approach will now cause a NullPointerException in Classloader.loadLibrary when running on openjdk. – 0-0 Feb 20 '20 at 06:00
5
  1. There is no approved way to change the library path for a running JVM.
  2. You cannot load a native library more than once ... and you cannot unload a native library so that you can load it again: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4171986

Based on your comments above (particularly, the behavior of the 3rd-party library), I'd say that your best option is to get the library path right when you launch the JVM.

Note that there is a hacky way to change the library path (see https://stackoverflow.com/a/24258955/139985) but it involves nasty reflection, and it reportedly doesn't work for all Java releases. Certainly, it relies on undocumented private implementation details of ClassLoader that could change from one release to the next.

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
2

Just recently ran into this issue and using OpenJDK where a NullPointerException is thrown (as @0-0 mentioned in his comment to Samil's answer). The following works in OpenJDK and should work with Oracle JDK as well.

(Option 1) Replace the java.library.path

System.setProperty("java.library.path", newPath);
Field field = ClassLoader.class.getDeclaredField("sys_paths");
field.setAccessible(true);
field.set(ClassLoader.getSystemClassLoader(), new String[]{newPath});

(Option 2) Add to existing java.library.path

String libPath = System.getProperty("java.library.path");
String newPath;

if (libPath == null || libPath.isEmpty()) {
    newPath = path;
} else {
    newPath = path + File.pathSeparator + libPath;
}

System.setProperty("java.library.path", newPath);

Field field = ClassLoader.class.getDeclaredField("sys_paths");
field.setAccessible(true);
    
// Create override for sys_paths
ClassLoader classLoader = ClassLoader.getSystemClassLoader(); 
List<String> newSysPaths = new ArrayList<>();
newSysPaths.add(path);  
newSysPaths.addAll(Arrays.asList((String[])field.get(classLoader)));
           
field.set(classLoader, newSysPaths.toArray(new String[newSysPaths.size()]));
Abra
  • 19,142
  • 7
  • 29
  • 41
Donny
  • 51
  • 2
1

I tried to following to load a native Growl library for a Java application on my Mac where the lib is in the root of the classpath of my application:

System.load(GrowlUtils.class.getResource("/libgrowl.jnilib").getFile().toString());
fimez
  • 408
  • 3
  • 14
  • 1
    I am able to load the library in my own code, that's no problem. The problem is that third-party code loads the library with `System.loadLibrary("libName");`, so I need a way to influence the behavior of this method, not another way to load a library. – Philipp Wendler Aug 28 '11 at 08:52
  • You could create a version of the class that uses a different call and place that version higher up in the classpath so it gets picked up first – fimez Jul 03 '14 at 11:05
  • Interesting idea, but quite ugly of course as you need to create that class (especially if its in a binary-only library) and you need to update it when the library gets updated. – Philipp Wendler Jul 03 '14 at 14:47
  • I admit it is a dirty hack and might cause some additional work when upgrading, but we've had success with this approach in fixing/changing stuff in Liferay that we couldn't touch otherwise. However we did always have to original source available and didn't need to start from a decompiled class. – fimez Jul 04 '14 at 11:17
0

Is there a simpler way?

Yes, provide batch/script files to start the application. Then you can set the correct path in the batch/shell file or even read the value from an environment variable. Much easier then trying to do it from inside the application.

Andreas Dolk
  • 113,398
  • 19
  • 180
  • 268
  • 3
    Yes, that's we currently do. But that doesn't work for users starting it from within an IDE, or using it as an Eclipse plugin. – Philipp Wendler Feb 16 '11 at 08:02
-1

While technically correct these answers are missleading. Setting the environment variables PATH on Windows, or LD_LIBRARY_PATH on unix will change where the jvm looks for libraries: What is LD_LIBRARY_PATH and how to use it?

on linux: export LD_LIBRARY_PATH=/usr/.../ then: java ....

Petr
  • 47
  • 7
  • I think that you have missed the point of the question. It is about changing the library path of a JVM **that has already been launched**. Changing environment variables won't help with that. It is too late. – Stephen C Feb 20 '20 at 06:38