Goodbye JNI: Embracing Java’s Foreign Function & Memory API
Goodbye JNI, Hello FFM API The Foreign Function & Memory (FFM) API is the modern, elegant replacement for older JEPs—namely JEP 393 (Foreign-Memory Access API) and JEP 389 (Foreign Linker API)—both introduced as incubator features in JDK 16. Its mission? To make the painful days of Java Native Interface (JNI) a distant memory. JNI, while historically essential for bridging Java and native code (typically C or C++), has major shortcomings: Verbose and fragile native method signatures Difficult and unsafe memory management Tight memory limits (~2 GB) Poor performance due to complex marshalling A traditional JNI example requires external tools like javah, native compilation steps, and tedious header files. All that just to interact with a native function. Memory, Managed Differently: The Foreign Memory API Enter the Foreign Memory API, which allows off-heap memory interaction—in pure Java—with built-in safety and high performance. At its core is the MemorySegment interface. This represents a block of memory and ensures: Bounds checking Prevention of use-after-free bugs Thread confinement for consistent access patterns Lifecycle Control with Arenas To handle memory allocation lifetimes, the FFM API introduces Arena, a powerful abstraction for memory management. Four types of arenas offer flexibility: Global: Never freed, useful for constants Auto: Memory is freed by the GC Confined: Lifetime bound to a scope (try-with-resources) Shared: Thread-safe memory with explicit cleanup JEP 312 (Thread-Local Handshakes) complements shared arenas by giving fine-grained memory cleanup control across threads. Reading & Writing Memory: A Rectangle Example Suppose we want to map this native C structure into Java: struct Rectangle { int width; int height; }; With FFM in Java, you can allocate and interact with it like this: MemoryLayout RECTANGLE = MemoryLayout.structLayout( ValueLayout.JAVA_INT.withName("width"), ValueLayout.JAVA_INT.withName("height") ); static final VarHandle WIDTH_H = RECTANGLE.varHandle(PathElement.groupElement("width")); static final VarHandle HEIGHT_H = RECTANGLE.varHandle(PathElement.groupElement("height")); try (Arena arena = Arena.ofConfined()) { MemorySegment rect = arena.allocate(RECTANGLE); WIDTH_H.set(rect, 0L, 40); HEIGHT_H.set(rect, 0L, 60); int width = (int) WIDTH_H.get(rect, 0L); int height = (int) HEIGHT_H.get(rect, 0L); System.out.println("Area: " + (width * height)); // Output: Area: 2400 } This approach avoids manual offset math and gives you structured access to native memory with full type safety and runtime checks. Goodbye Boilerplate: Meet jextract To call native functions, the Foreign Function API (part of FFM) works alongside a powerful tool: jextract. It generates Java bindings from C headers automatically. Example: calling C’s abs function from stdlib.h: jextract --output classes --target-package org.cstdlib /usr/include/stdlib.h Then, in Java: import org.cstdlib.stdlib; try (Arena arena = Arena.ofConfined()) { int result = stdlib.abs(-42); System.out.println(result); // Output: 42 } No JNI glue code. No native compilation headaches. Just pure Java, talking to native code. Summary The FFM API is a breath of fresh air for developers who’ve wrestled with JNI. With safer memory management, cleaner native interop, and tools like jextract, Java now feels at home even in low-level systems programming tasks. Whether you’re allocating native structs, calling C libraries, or managing off-heap buffers, FFM brings native power into Java—without sacrificing readability or safety.

Goodbye JNI, Hello FFM API
The Foreign Function & Memory (FFM) API is the modern, elegant replacement for older JEPs—namely JEP 393 (Foreign-Memory Access API) and JEP 389 (Foreign Linker API)—both introduced as incubator features in JDK 16. Its mission? To make the painful days of Java Native Interface (JNI) a distant memory.
JNI, while historically essential for bridging Java and native code (typically C or C++), has major shortcomings:
- Verbose and fragile native method signatures
- Difficult and unsafe memory management
- Tight memory limits (~2 GB)
- Poor performance due to complex marshalling
A traditional JNI example requires external tools like javah
, native compilation steps, and tedious header files. All that just to interact with a native function.
Memory, Managed Differently: The Foreign Memory API
Enter the Foreign Memory API, which allows off-heap memory interaction—in pure Java—with built-in safety and high performance.
At its core is the MemorySegment
interface. This represents a block of memory and ensures:
- Bounds checking
- Prevention of use-after-free bugs
- Thread confinement for consistent access patterns
Lifecycle Control with Arenas
To handle memory allocation lifetimes, the FFM API introduces Arena, a powerful abstraction for memory management. Four types of arenas offer flexibility:
- Global: Never freed, useful for constants
- Auto: Memory is freed by the GC
- Confined: Lifetime bound to a scope (try-with-resources)
- Shared: Thread-safe memory with explicit cleanup
JEP 312 (Thread-Local Handshakes) complements shared arenas by giving fine-grained memory cleanup control across threads.
Reading & Writing Memory: A Rectangle
Example
Suppose we want to map this native C structure into Java:
struct Rectangle {
int width;
int height;
};
With FFM in Java, you can allocate and interact with it like this:
MemoryLayout RECTANGLE = MemoryLayout.structLayout(
ValueLayout.JAVA_INT.withName("width"),
ValueLayout.JAVA_INT.withName("height")
);
static final VarHandle WIDTH_H = RECTANGLE.varHandle(PathElement.groupElement("width"));
static final VarHandle HEIGHT_H = RECTANGLE.varHandle(PathElement.groupElement("height"));
try (Arena arena = Arena.ofConfined()) {
MemorySegment rect = arena.allocate(RECTANGLE);
WIDTH_H.set(rect, 0L, 40);
HEIGHT_H.set(rect, 0L, 60);
int width = (int) WIDTH_H.get(rect, 0L);
int height = (int) HEIGHT_H.get(rect, 0L);
System.out.println("Area: " + (width * height)); // Output: Area: 2400
}
This approach avoids manual offset math and gives you structured access to native memory with full type safety and runtime checks.
Goodbye Boilerplate: Meet jextract
To call native functions, the Foreign Function API (part of FFM) works alongside a powerful tool: jextract
. It generates Java bindings from C headers automatically.
Example: calling C’s abs
function from stdlib.h
:
jextract --output classes --target-package org.cstdlib /usr/include/stdlib.h
Then, in Java:
import org.cstdlib.stdlib;
try (Arena arena = Arena.ofConfined()) {
int result = stdlib.abs(-42);
System.out.println(result); // Output: 42
}
No JNI glue code. No native compilation headaches. Just pure Java, talking to native code.
Summary
The FFM API is a breath of fresh air for developers who’ve wrestled with JNI. With safer memory management, cleaner native interop, and tools like jextract
, Java now feels at home even in low-level systems programming tasks.
Whether you’re allocating native structs, calling C libraries, or managing off-heap buffers, FFM brings native power into Java—without sacrificing readability or safety.