Using native libraries
Using native libraries from the JVM is not necessarily trivial, and should be avoided at all costs if somehow possible. When using a native library, you have to write interface code for it, and often native libraries are native to the platform — meaning that you'll write this thrice (1x macOS, 1x Windows, 1x Linux), and then possibly for mobile too. In addition, the libraries have to support all the common architectures.
If you still have to continue for some reason, these are your options for using native libraries from the JVM:
JNI (Java Native Interface)
- Old mechanism provided by Java for calling native code.
- Requires writing native C/C++ code with functions following a specific naming convention.
- Java code loads the native library using System.loadLibrary("library_name").
- Most complex to use, good performance, worst portability, since basically all Java versions
Example:
public class NativeExample {
static {
System.loadLibrary("mylibrary"); // Load the native library
}
public native int add(int a, int b);
}
#include <jni.h>
#include "NativeExample.h"
JNIEXPORT jint JNICALL Java_NativeExample_add(JNIEnv *env, jobject obj, jint a, jint b) {
return a + b;
}
JNA (Java Native Access)
- simpler alternative to JNI
- Instead of you having to write the mapping code in C, it will dynamically generate it
- Requires
jna.jar - Simple to use, bad performance, high portability, since basically all Java versions
import com.sun.jna.Library;
import com.sun.jna.Native;
public interface CLibrary extends Library {
CLibrary INSTANCE = Native.load("c", CLibrary.class);
void printf(String format, Object... args);
}
public class JNATest {
public static void main(String[] args) {
CLibrary.INSTANCE.printf("Hello from JNA\n");
}
}
JNR (Java Native Runtime)
- JNR is an alternative to JNA with better performance.
- Comparable to JNA, but higher performance
import jnr.ffi.LibraryLoader;
public interface CLibrary {
void printf(String format, Object... args);
}
public class JNRTest {
public static void main(String[] args) {
CLibrary lib = LibraryLoader.create(CLibrary.class).load("c");
lib.printf("Hello from JNR\n");
}
}
FFI (Java Foreign Function & Memory API) / Project Panama
- Available to some degree since JDK 17+ (incubating JEP 370 in JDK 14-16, JEP 413 in JDK 17-18), but realistically was LTS-ed in 21, and refined in 22
- safer, more efficient way to interact with native code
- Moderate ease of use with high performance and portability, but only available in pretty recent Java versions
import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;
public class PanamaTest {
public static void main(String[] args) throws Throwable {
Linker linker = Linker.nativeLinker();
SymbolLookup stdlib = linker.defaultLookup();
MethodHandle printf = linker.downcallHandle(
stdlib.find("printf").get(),
FunctionDescriptor.ofVoid(ValueLayout.ADDRESS, ValueLayout.ADDRESS)
);
try (Arena arena = Arena.ofConfined()) {
MemorySegment cString = arena.allocateUtf8String("Hello from Panama!\n");
printf.invoke(cString);
}
}
}
