Package Java JNI libraries in a JAR using Maven
Introduction
If you are using a native library in Java, you can just load the dll
or dylib
or so
files using System.loadLibrary
method. But if you are packaging your Java program as a JAR file, the native library will not be available to the System.loadLibrary
method. In this article, we’ll see the correct way to package the native library in the JAR file using Maven so that it will be accessible in our Java program without any other user intervention.
Project Structure
Maven follows a specific project structure where all the source code is placed in src/main/java
and all the resources are placed in src/main/resources
. The resources directory is added to the classpath when the project is compiled and packaged. This means you can access the contents of the resources
directory directly in the Java program by specifying the path relative to the resources directory.
Let’s say you have a native library libhello.dylib
in the resources/lib
directory. After packaging this will be available at the path lib/libhello.dylib
in the JAR file.
Use Native Utils
The default way to load a native library in Java is to use the System.loadLibrary
method. The maven package
command will generate the jar file of the project. The native library will be packaged in the lib
directory inside the jar file which will not be available to the System.loadLibrary
method.
Now running our app will require us to extract the library from the jar file and put it somewhere on the system. This can be done using Native Utils. Internally this utility is extracting the native library from the jar file to a temporary directory and loads the library from there. This will keep our hands out of the dirty work of extracting the library and loading it. It provides a method loadLibraryFromJar
which can be used to load the native library from the jar file.
Configurations
As Native Utils is not available as a release package, we’ll need to add it directly from the GitHub repository. For this, we can use the JitPack service which allows us to add GitHub repositories as Maven dependencies. You’ll need to add JitPack as a repository in your POM.XML
file.
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
Also, add the dependency for Native Utils. For this, we need to give groupId
as com.github.<user-name>
and artifactId
as <repository-name>
. Since we are using the latest commit from the repository, we’ll need to specify the commit hash in the version tag. Below is how the dependency will look like in POM.XML
.
<dependency>
<groupId>com.github.adamheinrich</groupId>
<artifactId>native-utils</artifactId>
<version>e6a39489662846a77504634b6fafa4995ede3b1d</version>
</dependency>
Now, you can confirm the dependency is added by running the maven dependency:tree
command.
> mvn dependency:tree
[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ javaunsafe ---
[INFO] com.gor.poc:javaunsafe:jar:1.0-SNAPSHOT
[INFO] +- log4j:log4j:jar:1.2.17:compile
[INFO] +- junit:junit:jar:3.8.1:test
[INFO] \- com.github.adamheinrich:native-utils:jar:e6a39489662846a77504634b6fafa4995ede3b1d:compile
You can see, my project is using 3 dependencies, log4j, junit, and native-utils with their version as specified.
Load the native library
In your Java file, you can call the loadLibraryFromJar
method to load the native library.
import cz.adamh.utils.NativeUtils;
public class NativeMemoryLoader {
static {
try {
NativeUtils.loadLibraryFromJar("/lib/libhello.dylib");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static native void sayHello();
...
}
Load library based on your OS
The problem with loading native libraries like C/C++ is that your compiled file is dependent on the platform. This means you’ll need to compile the library for each platform and then package it in the JAR file. So you’ll have different versions of the library for different platforms. And you’ll end up with so
, dylib
, and dll
files in your lib
directory. Let’s modify our Java code to use the library based on the platform.
// Get the file extension based on OS
String osName = System.getProperty("os.name").toLowerCase();
String libExtension = osName.contains("win") ? ".dll" :
osName.contains("mac") ? ".dylib" : ".so";
String libPath = "/lib/libhello" + libExtension;
NativeUtils.loadLibraryFromJar(libPath);
Conclusion
In this article, we saw how to package native libraries in a JAR file using Maven and load them in our Java program. We also saw how to load the library based on the platform.