How to Call Native C Functions from Java with Project Panama (JDK 19) – A Step‑by‑Step Guide
This article introduces Java's Project Panama foreign function and memory API, showing how to use JDK 19 to call native C code from a simple Hello World program, covering linker setup, symbol lookup, function descriptors, memory sessions, and method handles with practical code examples.
With the release of JDK 19, it is time to explore Project Panama, specifically the new Foreign Function & Memory API that simplifies interoperability between Java and native code.
Project Overview
The Panama project aims to bridge the JVM with native code written in languages such as C/C++. It consists of three parts:
Foreign Function & Memory API (JEP 424)
Jextract tool
Vector API (JEP 338)
Hello World Example
The tutorial uses a simple Java "Hello World" application that calls a native C printf function to demonstrate the API.
Linker
The linker acts as a bridge between the JVM and native C ABI. JDK 19 provides platform‑specific linker implementations:
public static Linker getSystemLinker() { return switch (CABI.current()) { case Win64 -> Windowsx64Linker.getInstance(); case SysV -> SysVx64Linker.getInstance(); case LinuxAArch64 -> LinuxAArch64Linker.getInstance(); case MacOsAArch64 -> MacOsAArch64Linker.getInstance(); }; }Downcalls invoke native functions, while upcalls allow native code to call back into Java.
Finding the Native Function Address
Linker linker = Linker.nativeLinker();
SymbolLookup linkerLookup = linker.defaultLookup();
SymbolLookup systemLookup = SymbolLookup.loaderLookup();
SymbolLookup symbolLookup = name -> systemLookup.lookup(name).or(() -> linkerLookup.lookup(name));
Optional<MemorySegment> printfMemorySegment = symbolLookup.lookup("printf");Proper error handling is required because symbol lookup may fail.
Creating a Function Descriptor
FunctionDescriptor printfDescriptor = FunctionDescriptor.of(JAVA_INT, ADDRESS);The descriptor defines a function returning an int and accepting a pointer (the C char* argument).
Building a Method Handle
MethodHandle printfMethodHandle = symbolLookup.lookup("printf")
.map(addr -> linker.downcallHandle(addr, printfDescriptor))
.orElse(null);This creates an executable reference to the native printf function.
Memory Allocation
Memory for native calls is allocated outside the Java heap using MemorySession or SegmentAllocator, which implement AutoCloseable for automatic cleanup.
try (var memorySession = MemorySession.openConfined()) {
SegmentAllocator allocator = SegmentAllocator.newNativeArena(memorySession);
var cStringFromAllocator = allocator.allocateUtf8String("Hello World
");
var cStringFromSession = memorySession.allocateUtf8String("Hello World
");
}Calling the Native Function
private static int printf(String str, MemorySession memorySession) throws Throwable {
Objects.requireNonNull(printfMethodHandle);
var cString = memorySession.allocateUtf8String(str + "
");
return (int) printfMethodHandle.invoke(cString);
}
public static void main(String[] args) throws Throwable {
var str = "Hello World";
try (var memorySession = MemorySession.openConfined()) {
System.out.println(printf(str, memorySession));
}
}Summary
The article covered the Foreign Function & Memory API, showing how to locate native symbols, define function descriptors, allocate off‑heap memory, and invoke native functions from Java using method handles. The jextract tool can automate much of the boilerplate.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
