Package Java JNI libraries in a JAR using Maven

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.