The FFM API: OpenJDK Changed the Game for Native Interactions and #JavaOnRaspberryPi

If you’re working with Pi4J, you’re essentially using a Java library that bridges the gap between your Java application and the GPIO hardware on a Raspberry Pi. The beauty of Pi4J lies in how it hides the complexity of native library integration and the Java Native Interface (JNI). It lets you focus on building great applications rather than dealing with low-level system calls.

My involvement with Pi4J started around 2020 while writing the book “Getting Started with Java on the Raspberry Pi“. Despite years of contributing to the project, the deeper layers of Pi4J’s codebase still challenge me. Take a look at this snippet and tell me if you understand what’s going on:

JNIEXPORT jobject JNICALL Java_com_pi4j_library_gpiod_internal_GpioD_c_1gpiod_1chip_1open
  (JNIEnv* env, jclass javaClass, jstring path) {
    struct gpiod_chip* chip;
    const char* nativeString = (*env)->GetStringUTFChars(env, path, NULL);
    chip = gpiod_chip_open(nativeString);
    (*env)->ReleaseStringUTFChars(env, path, nativeString);
    if(chip == NULL) {
      return NULL;
    }
    jclass cls = (*env)->FindClass(env, "java/lang/Long");
    jmethodID longConstructor = (*env)->GetMethodID(env, cls, "<init>", "(J)V");
    return (*env)->NewObject(env, cls, longConstructor, (jlong) (uintptr_t) chip);
}

Honestly, I, as a Java developer, have no clue… It’s one of those points where Java meets native libraries for GPIO communication. Through a combination of JNI, Java Native Access (JNA), and Docker-based build pipelines for compiling native components, Pi4J presents a clean interface to users while creating maintenance headaches for the development team.

This article explores how the Foreign Function & Memory API (FFM API) has transformed native library interaction in Java, bringing significant improvements to projects such as Pi4J.

Looking Back at Java’s Evolution

Fifteen years ago, I moved from C# development to Java and haven’t regretted it for a moment. That transition happened right at Java’s 15-year mark, halfway through what is now a three-decade journey. I’ve been an active observer of Java’s growth ever since and had the privilege of interviewing James Gosling, Java’s creator, for the Foojay Podcast.

The six-month release cycle, driven by OpenJDK projects and JDK Enhancement Proposals (JEPs), has delivered remarkable improvements, including enhanced switch expressions, virtual threads, continuous performance optimization, and more. In this article we focus on one of these innovations, delivered within Project Panama: the Foreign Function & Memory API (FFM API).

Understanding the Foreign Function & Memory API

Java 22 marked the official arrival of the FFM API as a fully integrated feature. This milestone represents extensive work under Project Panama’s umbrella, driven by three core objectives:

  1. Memory safety: Secure handling of off-heap memory with guaranteed resource cleanup.
  2. Easy interaction: Straightforward mechanisms for native library integration.
  3. High performance: Performance that rivals or surpasses traditional JNI.

The JNI Challenge

Since Java 1.1, JNI has served as the bridge to native code. While functional, it brings several significant pain points:

  • Manual memory management requires deep expertise.
  • Complex implementation involving C header generation and compilation workflows.
  • Hard to use by design: Simon Ritter once shared that a Sun engineer deliberately designed JNI to be difficult, hoping to discourage its use!

Various libraries, such as JNA and Java Native Runtime (JNR), have attempted to simplify native integration, but each has its own performance penalties and constraints.

FFM API’s Development Journey

The FFM API’s evolution demonstrates OpenJDK’s approach to introducing new features. The work was structured into multiple JEPs, with initial deliveries appearing in Java 14. Notice how most features progressed through incubator or preview stages, requiring the --enable-preview flag, a process I explained in this Azul documentation page.

The initial phase, “Foreign Memory Access API,” introduced a new approach for accessing off-heap memory safely and efficiently. These early versions remained in incubator status, providing the foundation for the next steps…

That next step, “Foreign Linker API,” brought statically typed, pure-Java access to native code, still in incubator status.

In the final step, the pieces of the puzzle got combined in the “Foreign Function & Memory API“. After several incubator and preview releases, the final version became fully available in Java 22.

This process enables the OpenJDK team to get community feedback and refine the API until it achieves the right balance of stability, performance, and usability.

Practical Examples

Let’s take a look at a few simple examples to illustrate how the FFM API can be used.

Direct Memory Operations

Here’s how you can work with memory directly:

void main() {
    // Open a confined Arena that manages off-heap memory
    // and will release it automatically.
    try (Arena arena = Arena.ofConfined()) {
        // Allocate 5 ints in the arena
        MemorySegment segment = arena.allocate(ValueLayout.JAVA_INT, 5);

        // Fill the segment with random values for each int.
        System.out.print("Setting values: ");
        for (int i = 0; i < 5; i++) {
            int randomValue = new Random().nextInt(100);
            segment.setAtIndex(ValueLayout.JAVA_INT, i, randomValue);
            System.out.print(randomValue + " ");
        }
        System.out.println("");

        // Print the values back from memory.
        System.out.print("Reading values: ");
        for (int i = 0; i < 5; i++) {
            System.out.print(segment.getAtIndex(ValueLayout.JAVA_INT, i) + " ");
        }
        System.out.println("");
    }
}

Several aspects of this code are worth highlighting:

  • Running on Java 25 lets me take advantage of the simplified main method from JEP 512: Compact Source Files and Instance Main Methods. No class boilerplate, no package declarations, but just the essential code for this straightforward demonstration.
  • The Arena handles automatic cleanup within the try block.
  • MemorySegment provides a clean abstraction over native memory addresses.
  • Manual memory management becomes unnecessary.
$ java ArenaDemo.java
Setting values: 16 7 27 50 80 
Reading values: 16 7 27 50 80 

For a deeper dive, check out the expanded example in FFMMemoryManagement.java, alongside a Java 11 comparison in Java11MemoryManagement.java that demonstrates the old complexity. The Java 11 version requires JBang execution following the instructions in the file to enforce Java 11 usage.

Native Function Invocation

Let’s create the world’s most overengineered String.length() Implementation to demonstrate native function calls! This example shows how the FFM API enables clean native integration within a single, readable method.

void main() throws Throwable {
    // The text we will use in the demo.
    var text = "Hello JVM Advent!";

    // Obtain a Linker that knows how to call
    // native (C) functions on this platform.
    Linker linker = Linker.nativeLinker();

    // Create a SymbolLookup that can find symbols
    // (like C functions) in the default native libraries.
    SymbolLookup lookup = linker.defaultLookup();

    // Create a MethodHandle to call the native C function
    MethodHandle strlen = linker.downcallHandle(
        // Look up the address of the `strlen` symbol,
        // or throw if it isn't found.
        lookup.find("strlen").orElseThrow(),
        // Describe the C function:
        // - returns long
        // - takes one pointer (address) argument
        FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS)
    );

    // Open a confined Arena that manages off-heap memory
    // and will release it automatically.
    try (Arena arena = Arena.ofConfined()) {
        // Allocate native memory for a C-style string
        // and copy "Hello World!" into it.
        MemorySegment str = arena.allocateFrom(text);

        // Call the native `strlen` function via the MethodHandle,
        // passing the string's memory address,
        // and cast the result to a long.
        long length = (long) strlen.invoke(str);

        // Print the result.
        System.out.println("Length with native library: " + length);
    }

    System.out.println("Length with String.length(): " + text.length());
}

No complex JNI/JNA needed, no recompilation of C code, but just simple and readable Java code.

$ java LinkerDemo.java

Length with native library: 17
Length with String.length(): 17

Additional examples are available: extended FFM version in FFMNativeCalls.java, plus the Java 11 equivalent in Java11NativeCalls.java.

Benchmarking Performance

I’m a fan of demos with visual feedback, so I built a benchmark that generates animated gradients, a task that requires extensive memory writes. Both the Java 11 and 25 versions attempt a 5-millisecond refresh interval. The performance difference is striking (on macOS M2 with Azul Zulu 25 versus Java 11:

$ jbang Java11PixelBuffer.java
[jbang] Building jar for Java11PixelBuffer.java...
Interval: 57 - Generated 250000 pixels
Interval: 56 - Generated 250000 pixels

$ java FFMPixelBuffer.java
Interval: 5 - Generated 250000 pixels
Interval: 5 - Generated 250000 pixels

FFM API’s Impact on Raspberry Pi Development

Let me first answer a more general question I get asked a lot: “Why Java on a Raspberry Pi?” My answer is straightforward: Java allows me to create everything I need. I can develop JavaFX interfaces, integrate with web services, and use the many libraries available within Java’s ecosystem. When I began exploring Java on Raspberry Pi over five years ago, learning another language wasn’t my goal. I wanted to understand electronics and hardware interaction using familiar, proven tools.

Pi4J serves this purpose precisely: it’s a Java library for controlling GPIO pins and connected electronics on Raspberry Pi. The complication? This library has always depended on native C/C++ code for hardware communication. Until recently, that meant dealing with the complexities of JNI and JNA.

How Pi4J Works

Pi4J implements a plugin architecture where different “providers” manage GPIO communication. Historically, these providers depended on native libraries via JNI/JNA, creating:

  • Multi-layered complexity spanning up to 5 levels for GPIO operations.
  • Docker-based build systems for native library compilation.
  • Mysterious JNI header generation and arcane C implementations.

These plugins load dynamically during runtime, shown as the “black bar” in this architecture diagram:

The FFM Revolution

Pi4J’s upcoming V4 release includes a nearly production-ready FFM-based provider. The improvements are substantial:

  • Performance Gains
    • Reading GPIO input state: 10x performance increase!
    • SPI communication: Notable improvement, though less dramatic.
  • Code Quality
    • Direct Java-to-kernel communication paths.
    • Elimination of complex JNI layers.
    • Clear, maintainable Java code throughout.
    • No complex Docker build infrastructure needed.

Community Contribution SUCCESS

The FFM implementation has an even better backstory: it came from community contributions. Nick Gritsenko (@DigitalSmile) had already developed a Java 22 GPIO library using FFM. When I found his work, I asked him if we could use it in Pi4J. His response was better than expected as he contributed the code directly to Pi4J and adapted it perfectly to our plugin architecture! This led to a substantial pull request now merged into Pi4J V4.

I’m very proud that this happens again and again. Previously, Alexander Liggesmeyer contributed Raspberry Pi 5 support through a new plugin. Currently, Stefan Haustein, Stephen More, Tom Aarts, and other contributors are building an easy-to-use Pi4J drivers library with example implementations, making it a lot easier to work with complex components like joysticks, LCD displays, LED strips, and more.

Expanding Hardware Support

The FFM-based implementation opens up exciting possibilities. Since it relies on standard Linux kernel methods from Debian, Pi4J should theoretically support:

  • Orange Pi and similar Linux-based Single Board Computers.
  • RISC-V processors running Debian distributions.

I’m starting to experiment with these alternative platforms and hope to add more documentation about it on the Pi4J website soon.

Pi4J Code Examples with FFM

Because the FFM plugin in Pi4J fits perfectly into the architecture, there is nothing special you, as a developer, need to do. Include the plugin in your pom-file, instead of the plugin you were using before, and you’re done!

Here’s a condensed example using JBang to blink an RGB LED with Pi4J V4’s snapshot build. After the official release, you can remove the //REPOS line and update the version number.

/// usr/bin/env jbang "$0" "$@" ; exit $?

//REPOS mavencentral,pi4j-snapshots=https://oss.sonatype.org/content/repositories/snapshots

//DEPS com.pi4j:pi4j-core:4.0.0-SNAPSHOT
//DEPS com.pi4j:pi4j-plugin-linuxfs:4.0.0-SNAPSHOT

import com.pi4j.Pi4J;
import com.pi4j.io.gpio.digital.*;

/**
 * Execute with `jbang Pi4JExample.java`
 */
void main() throws InterruptedException {
    var pi4j = Pi4J.newAutoContext();

    var red = pi4j.digitalOutput().create(17);
    var green = pi4j.digitalOutput().create(27);
    var blue = pi4j.digitalOutput().create(22);

    // Blink pattern
    for (int i = 0; i < 10; i++) {
        red.high();
        Thread.sleep(500);
        red.low();
        green.high();
        Thread.sleep(500);
        green.low();
        blue.high();
        Thread.sleep(500);
        blue.low();
    }
}

Find additional JBang-compatible examples in the Pi4J JBang repository.

Key Considerations

While preparing my presentation and this article about the FFM API, several important points emerged.

  • Memory Safety Warning: FFM usage takes you outside the JVM’s garbage collector safety net. Several examples included in this article, generate warnings about native access. This is intentional as the OpenJDK team wants developers to recognize they’re performing potentially hazardous operations. Add the --enable-native-access flag to acknowledge the risks and suppress warnings. Future OpenJDK versions may change this behavior.
  • Read JEPs To Learn More: If Java’s evolution interests you, I strongly recommend exploring JDK Enhancement Proposals. They’re far more than technical specs! These documents explain design decisions, include practical examples, and offer insights from Java’s architects. Similarly, you can follow OpenJDK project progress on their webpages and by subscribing to their mailing lists.
  • Follow Java Releases: Java’s six-month release cycle consistently delivers valuable improvements! Each version brings numerous bug fixes, optimizations, and new features emerging from projects and JEPs. If you can upgrade to a newer version, particularly for development environments, you should! While Java 22 introduced the FFM API, I learned at Devoxx that Java 24 delivered significant FFM performance improvements without API changes! This demonstrates the OpenJDK team’s ongoing commitment to enhancing existing features.

Wrapping Up

The FFM API represents a significant leap forward for developers working with native code. It eliminates JNI complexity while maintaining or improving performance. It creates new possibilities for Java in embedded systems and hardware integration. It will accelerate the closer integration of Java with Artificial Intelligence, Machine Learning, and Large Language Model development.

Don’t hesitate to experiment and contribute. Projects like OpenJDK and Pi4J grow through community participation!

For Java on Raspberry Pi exploration:

Don’t forget: We owe thanks to OpenJDK and its contributors for incredible features like the Garbage Collector, JIT compilation, Lambdas, Virtual Threads, and now the FFM API. Java continues improving, and that’s something worth celebrating!

Want to Dive Deeper?
Frank Delporte is a speaker at JCON!
This article covers the topic of his JCON talk. If you can’t attend live, the session video will be available after the conference – it’s worth checking out!

Total
0
Shares
Previous Post

Architect Your Own Experience: Creating Your Individual JCON 2026 Journey

Next Post

Free Java Training Videos Now Available: From Fundamentals to Modern Frameworks

Related Posts