How to Load Native JNI Library from JAR

The JNI (Java Native Interface) is a framework that provides a bridge between Java and native applications. Java applications can define native methods which are implemented in dynamic library written in other languages such as C or C++. The dynamic library is able to call both static and dynamic methods. More information about JNI can be found on Wiki or in tutorial Beginning JNI with NetBeans (for Linux).

The problem is that for loading such a dynamic library you have to call method System.load(String filename) which requires an absolute filename. This approach is just fine if you have dynamic library outside the application’s JAR archive, but when bundling dynamic library within the JAR it is necessary to extract the library into filesystem before loading it. And that’s exactly what my code does.

Our simple JNI class could look like this:

1
2
3
4
5
6
7
public class HelloJNI {
  static {
    System.load("/path/to/my/library.so");
  }
 
  public native void hello();
}

To extract the library before loading it it’s necessary to add some code into the static section. I wrapped it into a static method inside simple class called NativeUtils. I decided to put it into separate class in order to have space for adding more features (like choosing the right dynamic library for host OS and architecture):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
package cz.adamh.utils;
 
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
 
/**
 * Simple library class for working with JNI (Java Native Interface)
 * 
 * @see http://frommyplayground.com/how-to-load-native-jni-library-from-jar
 *
 * @author Adam Heirnich <adam@adamh.cz>, http://www.adamh.cz
 */
public class NativeUtils {
 
    /**
     * Private constructor - this class will never be instanced
     */
    private NativeUtils() {
    }
 
    /**
     * Loads library from current JAR archive
     * 
     * The file from JAR is copied into system temporary directory and then loaded. The temporary file is deleted after exiting.
     * Method uses String as filename because the pathname is "abstract", not system-dependent.
     * 
     * @param filename The filename inside JAR as absolute path (beginning with '/'), e.g. /package/File.ext
     * @throws IOException If temporary file creation or read/write operation fails
     * @throws IllegalArgumentException If source file (param path) does not exist
     * @throws IllegalArgumentException If the path is not absolute or if the filename is shorter than three characters (restriction of {@see File#createTempFile(java.lang.String, java.lang.String)}).
     */
    public static void loadLibraryFromJar(String path) throws IOException {
 
        if (!path.startsWith("/")) {
            throw new IllegalArgumentException("The path has to be absolute (start with '/').");
        }
 
        // Obtain filename from path
        String[] parts = path.split("/");
        String filename = (parts.length > 1) ? parts[parts.length - 1] : null;
 
        // Split filename to prexif and suffix (extension)
        String prefix = "";
        String suffix = null;
        if (filename != null) {
            parts = filename.split("\\.", 2);
            prefix = parts[0];
            suffix = (parts.length > 1) ? "."+parts[parts.length - 1] : null; // Thanks, davs! :-)
        }
 
        // Check if the filename is okay
        if (filename == null || prefix.length() < 3) {
            throw new IllegalArgumentException("The filename has to be at least 3 characters long.");
        }
 
        // Prepare temporary file
        File temp = File.createTempFile(prefix, suffix);
        temp.deleteOnExit();
 
        if (!temp.exists()) {
            throw new FileNotFoundException("File " + temp.getAbsolutePath() + " does not exist.");
        }
 
        // Prepare buffer for data copying
        byte[] buffer = new byte[1024];
        int readBytes;
 
        // Open and check input stream
        InputStream is = NativeUtils.class.getResourceAsStream(path);
        if (is == null) {
            throw new FileNotFoundException("File " + path + " was not found inside JAR.");
        }
 
        // Open output stream and copy data between source file in JAR and the temporary file
        OutputStream os = new FileOutputStream(temp);
        try {
            while ((readBytes = is.read(buffer)) != -1) {
                os.write(buffer, 0, readBytes);
            }
        } finally {
            // If read/write fails, close streams safely before throwing an exception
            os.close();
            is.close();
        }
 
        // Finally, load the library
        System.load(temp.getAbsolutePath());
    }
}

The code is commented and self-explaining, so I don’t have to write too much about it. Just three notes:

  • The file path is passed as string, not as instance of File. It is because File transforms the abstract path to system-specific (absolute path decision, directory delimiters) one, which could cause problems. It must be an absolute path (starting with ‘/’) and the filename has to be at least three characters long (due to restrictions of File.createTempFile(String prefix, String suffix).
  • The temporary file is stored into temp directory specified by java.io.tmpdir (by default it’s the operating system’s temporary directory). It should be automatically deleted when the application exits.
  • Although the code has some try-finally section (to be sure that streams are closed properly in case an exception is thrown), it does not catch exceptions. The exception has to be handled by the application. I belive this approach is cleaner and has some benefits.

Final usage is pretty simple. :-) Just call method loadLibraryFromJar and handle exception somehow:

1
2
3
4
5
6
7
8
9
10
11
12
13
import cz.adamh.NativeUtils;
 
public class HelloJNI {	 
  static {	 
    try {	 
      NativeUtils.loadLibraryFromJar("/resources/libHelloJNI.so");	 
    } catch (IOException e) {	 
      e.printStackTrace(); // This is probably not the best way to handle exception :-)	 
    }	 
  }	 
 
  public native void hello();	 
}

Edited 2013-04-02: Lofi came with a workaround to release and delete our DLL from temporary directory on Widnows.

Give me some love! :-) If you enjoyed this post, please consider leaving a comment or subscribing to the RSS feed to have future articles delivered to your feed reader.
This entry was posted in Programming and tagged , . Bookmark the permalink.

12 Responses to How to Load Native JNI Library from JAR

  1. davs says:

    The article is great.
    One little remark ..

    Lines 60-61:
    // Prepare temporary file
    File temp = File.createTempFile(prefix, suffix);

    in your case suffix contain only extension (without ‘.’ before) .. And I was getting the error of FileNotFound in line 91.

    Changing Line 61 to
    File temp = File.createTempFile(prefix, “.” + suffix);
    fixed my issue .. maybe not only mine :)

    Cheers,
    Davs

    • Adam says:

      Thank you, davs. Sorry for my fault, the temporary file was really “libraryso” instead of “library.so”. But I didn’t have any problems caused by the FileNotFound with my code, maybe it was some OS-specific issue (I run application using this code on Linux).

      I modified line 52 from suffix = (parts.length > 1) ? parts[parts.length - 1] : null; to suffix = (parts.length > 1) ? "."+parts[parts.length - 1] : null;, because the File.createTempFile(prefix, suffix) method uses “.tmp” as default extension if suffix is null.

      I also changed split() on line 50, it is now limited to 2 items.

      Eveything should be OK now :-)

  2. Eric Newhuis says:

    This approach works fine for my situation when the shared library has no other library dependencies. However my JNI native library is linked to another shared library and although this approach finds the JNI library it in turn generates an exception. In this case I am wrapping the Java multicast DNS library libjdns_sd.so that in turn depends on libdns_sd.so. I’m not sure what the best approach is to deal with this.

    :Exception in thread “main” java.lang.UnsatisfiedLinkError: /tmp/libjdns_sd.so: libdns_sd.so: cannot open shared object file: No such file or directory

  3. Lofi says:

    Thank you very much for sharing this! However, there’s a slight hickup in the solution: The temporary library won’t get deleted on windows because the JVM still has a lock on it when it shuts down and invokes deleteOnExit(). The library should be “unloaded”. Unfortunately that’s currently not possible in Java.

    In case anyone else runs into this issue of having their temporary folder flooded with DLLs, I came up with this workaround:

    * in additon to the temporary DLL, also create a .lock file on which you also issue a deleteOnExit(). This file will get deleted when the JVM shuts down

    * during startup search for all .dll files and delete those which don’t have an accompanying “.lock” file

    Not quite the proper solution, but unless this is fixed internally in Java it may be some workaround :-)

    Here’s some code in case someone wants to use it, just put it at the bottom of the loadLibraryFromJar method:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    
            final String libraryPrefix = prefix;
            final String lockSuffix = ".lock";
     
            // create lock file
            final File lock = new File( temp.getAbsolutePath() + lockSuffix);
            lock.createNewFile();
            lock.deleteOnExit();
     
            // file filter for library file (without .lock files)
            FileFilter tmpDirFilter =
              new FileFilter()
              {
                public boolean accept(File pathname)
                {
                  return pathname.getName().startsWith( libraryPrefix) && !pathname.getName().endsWith( lockSuffix);
                }
              };
     
            // get all library files from temp folder  
            String tmpDirName = System.getProperty("java.io.tmpdir");
            File tmpDir = new File(tmpDirName);
            File[] tmpFiles = tmpDir.listFiles(tmpDirFilter);
     
            // delete all files which don't have n accompanying lock file
            for (int i = 0; i < tmpFiles.length; i++)
            {
              // Create a file to represent the lock and test.
              File lockFile = new File( tmpFiles[i].getAbsolutePath() + lockSuffix);
              if (!lockFile.exists())
              {
                System.out.println( "deleting: " + tmpFiles[i].getAbsolutePath());
                tmpFiles[i].delete();
              }
            }
  4. Aurélien says:

    Thanks, useful trick

  5. Baruch Youssin says:

    A great solution, very simple, much simpler than all the competing solutions posted on the net.
    There is one thing in this article that I do not understand: the issue of “absolute path (starting with ‘/’)”.
    This refers to the
    path
    parameter in the code, which indicates the native library as a resource inside the jar.
    This far, I have worked only with audio files as resources, and their paths should not start with a slash / .
    In fact, I have tried to specify audio files by a path starting with a slash, as /com/…; here com was the name of the top-level directory in the jar, and the rest of the path pointed to the file-resource. This did not work out (java could not find the resource), neither on Windows XP nor on Linux Ubuntu 12.04, both running a most recent java from Oracle.
    Removing the initial slash resolves the problem: java finds the audio file.
    I am going to try both ways with the native library; but what is the rule here?

    • Baruch Youssin says:

      I have found the answer:
      loadLibraryFromJar calls java.lang.Class.getResourceAsStream(String) method while for audio files I use a different method, javal.lang.ClassLoader.getResource(String).
      These two methods have different rules for path name resolution, and for this reason in getResource(..) I must use the path without the leading / while in getResourceAsStream(..) I must use the leading /.
      Thanks for the nice code, it works!

  6. Lars Nielsen says:

    Hey I am trying this with a folder tructure
    /demo
    –/src
    —– App.java
    —– /com.core
    —– NativeUtils.java
    –/Libs
    —– libs.jar

    with the App.java looking like this:

    import java.io.IOException;

    import com.core.NativeUtils;

    public class App {

    public static void main(String[] args) {
    try {
    NativeUtils.loadLibraryFromJar("/lib/lib_fifi.so");
    System.out.println("Library Loaded - Loaded from jar");
    } catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    }
    }
    }

    But it does not work any ideas I get: File /lib/lib_fifi.so was not found inside JAR

  7. Octavio says:

    I was suggested this website by waay of myy cousin. I’m not positive whhether
    or not this submit is written through him as nobody else know such
    targeted approximately my problem. You’re wonderful!
    Thank you!

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>