Using C Libraries in Java 1: Fundamentals of the Foreign Function & Memory API
The Foreign Function & Memory API in Java provides significantly easier access to functions in C libraries than the outdated JNI.
(Image: SWstock / Shutterstock.com)
Java's Foreign Function & Memory API (FFM) is used to access code in a shared library or DLL written in a programming language like C or Rust. However, the code must meet certain prerequisites. This three-part article series uses a demo library written in C to show how a Java application calls the library's functions, what preparations are necessary, and what rules must be observed. The collective term “shared library” in the articles refers equally to a shared library on Unix and a Windows DLL.
The starting point for working with FFM was my search for a way to access a hardware security module (HSM) via Java. Since no physical HSM was available yet, I looked for a software-based implementation. The SoftHSM2 application can be addressed with PKCS11, but Sun's Pkcs#11 driver is outdated. Since I couldn't find a suitable open-source application, I developed a PKCS11 wrapper for Java myself based on the FFM API.
As the project is very extensive, this three-part article series focuses on a custom-developed C library designed to explain the concepts of the FFM API. The small demo library has been tested on Windows and Linux.
A Little History
Before FFM, Java had the Java Native Interface (JNI) for a long time, providing a way to access code written in C. However, JNI was very complicated and error-prone.
Videos by heise
Therefore, work began on a new interface in JDK 14 (Java Development Kit): Foreign Function & Memory API. The Java community refined it over several JDK versions and JEPs, finally finalizing it in JDK 22. However, it reappeared in JDK 24 in a modified form. Due to some breaking changes, the API from Java 24 is incompatible with that in Java 22. This article describes the current version from JDK 24.
To use the FFM API, the following prerequisites apply:
- A JDK from version 24 onwards must be installed.
- The operating system must be Windows or Linux on an x64 basis. The demo app should also work on macOS, although I haven't performed any tests for that.
- A Windows DLL or a shared library for Linux in a 64-bit version must be available.
- The DLL or shared library must be written in a language that supports the C ABI (Application Binary Interface). This includes, in addition to C and C++ (with appropriately declared functions), other languages like Rust and Go.
- When accessing the shared library, you must allow native access. This is currently possible without restrictions, which might change in a later Java version.
Description of DemoLib
The starting point for FFM is always a header file that describes the functions and, if applicable, the types of the shared library in C.
The example library developed in C contains only a few functions and one data type:
#ifdef _WIN32
#define EXPORT __declspec(dllexport)
#else
#define EXPORT
#endif
typedef struct
{
double x;
double y;
} Point;
#define VERSION 1
EXPORT void initialize(void);
EXPORT int getVersion(void);
EXPORT void getVersion2(int *version);
EXPORT long add(long a, long b);
EXPORT double calcAverage(int *lvalues, int size);
EXPORT double distance(Point *p1, Point *p2);
There is only a single type definition (Point) and a few functions. The directive #ifdef in the header file ensures that the code can be compiled on both Linux and Windows.
Tool Integration with Pitfalls
The jextract tool helps in accessing native functions. The starting point here is again a header file to generate the necessary access methods for the functions from the shared library.
However, jextract struggles with various difficulties. Firstly, it is not available for every JDK – after JDK 22, only again for JDK 25. For the demo library for the article, the version from JDK 22 generated two classes: Point for accessing the data structure and DemoLib_h for accessing the functions. The Point class has a size of about 170 lines of poorly readable code, and the DemoLib_h class has another 390 lines of code that are also difficult to read.
With complex header files, using jextract is even more difficult. When trying to generate a wrapper for PKCS11, jextract aborted in conjunction with JDK 22. The header file pkcs11.h loads two additional header files. This led to an abort with error messages indicating incompatible type redefinitions.
jextract is currently only usable for small projects -- and even then with limitations. Due to the hard-to-read code, it is not a template for your code. Therefore, the much better approach is to develop the code yourself and build the necessary expertise to understand it.