
In our last blog, I talked about one of the new JDK21 features viz. Virtual Threads. This blog I am going to talk about one more API that has been a complete rewrite. I will talk about the new way of interacting with foreign function, or what is also called the native functions and libraries. I will provide various examples that should cover a lot of typical scenarios that you may normally encounter during your programming.
Java from its infancy has always provided a way of interacting with native codes. JNI or Java Native Interface was the de-facto way of calling applications or libraries that were written in C or C++. This was critical when Java itself did not perform too well and any application needing access to native code or performant code will be coded to interact using JNI.
JNI suffered from a lot of different drawbacks, Some of the problems that we had included,
- Memory Management for programs written in C was entirely done by the developer. So when these APIs were called from Java, and memory that was allocated and was never freed, gave way to a lot of memory leaks. This could eventually cause the entire system to become unstable.
- If foreign functions were defined improperly, they were not checked resulting in incorrect response.
- JNI has additional overhead that can cause performance to be degraded a lot.
- Code becomes a lot more complex and non portable. It may become very difficult to debug issues when something comes up in the JNI abstraction layers.
JDK21 introduces a new foreign function interface (FFI), however it is still provided as a preview feature. I will add a table of contents below to aid jumping to individual examples.
New Foreign Function API
The new foreign function API provides a much more efficient implementation for communicating with native process. Java memory management can manage memory, or it can be left entirely to the developer (see Arena for more details).
Originally incubated as Project Panama, they wanted to remove writing the intermediate code that is used in JNI to interact with native codes. It also wanted the allocated memory to be managed by JVM rather than natively. This API provides a set of classes for the following,
- Controlling memory allocation and deallocation (Arena)
- Classes to access this foreign memory (Memory Segment, Memory Address, Segment Allocator)
- Define address space similar to C structure (Memory Layout)
- Look at the memory address where function load (Method Handles)
- Foreign function bridging (Linker, Symbol Lookup, Function Descriptor)
Let’s discuss some of the important one.
Arena

Let’s consider how Java manages memory. If you see the image on top, any memory allocation request from Java application will maintain a pointer in Eden memory. As this memory starts not getting needed, it keeps on getting pushed through Survivor to Old generation and eventually garbage collected. However, Java also manages Permanent Generation memories, that will potentially never be garbage collected. Rest of the memory where other applications will be running falls under Native memory (outside the jurisdiction of JVM).
Arena can manage that part of the memory which on the image above is called Native Memory. Arena controls the lifecycle of these native memory segments. To get some memory allocations, we need to first initialize arena and tell it how we want the memory management handled. Arenas can be split in 4 different types based on their characteristics.
Kind | Bounded Lifetime | Explicitly Closable | Accessible from multiple threads |
---|---|---|---|
Global | No | No | Yes |
Automatic | Yes | No | Yes |
Confined | Yes | Yes | No |
Shared | Yes | Yes | Yes |
When creating a single threaded application, it is easier to just create a confined arena using Arena.ofConfined(). After this is done, all native memory allocated will be monitored by arena and thus can be freed when program exits. It makes it easier to develop code without going into the intricacies of malloc() and free(), equivalent C calls to allocate and deallocate memory.
Linker
Linker is used to mediate between JVM code and native codes. It has the knowledge of how to invoke the native application. However, to use a linker, you will need to first create a method signature that is usable by the linker. The foreign function interface defines a full set of translations of C data types so that we can define the memory layout. Following table presents the set of corresponding Java values.
C Type | Layout | Java Type |
---|---|---|
bool | ValueLayout.JAVA_BOOLEAN | boolean |
char | ValueLayout.JAVA_BYTE | byte |
short | ValueLayout.JAVA_SHORT | short |
int | ValueLayout.JAVA_INT | int |
long | ValueLayout.JAVA_LONG | long |
long long | ValueLayout.JAVA_LONG | long |
float | ValueLayout.JAVA_FLOAT | float |
double | ValueLayout.JAVA_DOUBLE | double |
size_t | ValueLayout.JAVA_LONG | long |
char* , int** , struct Point* | ValueLayout.ADDRESS | MemorySegment |
int (*ptr)[10] | ValueLayout.ADDRESS.withTargetLayout( MemoryLayout.sequenceLayout(10, ValueLayout.JAVA_INT) ); | MemorySegment |
struct Point { int x; long y; }; | MemoryLayout.structLayout( ValueLayout.JAVA_INT.withName(“x”), MemoryLayout.paddingLayout(32), ValueLayout.JAVA_LONG.withName(“y”) ); | MemorySegment |
We will discuss about the rest of the functions as we progress through the codes. Let’s start writing some codes now.
Example Implementations
The first example is from Oracle’s examples. We will use strlen C function and create a foreign function interface on it.
Finding String Length (strlen)
/* * Calling String Length */ public void runNative01_Len(final String str) { try(final Arena arena = Arena.ofConfined()) { // Allocate off heap memory to store the string final MemorySegment nativeString = arena.allocateUtf8String(str); // Get Stdlib from Native Linker & get signature of String Length final Linker linker = Linker.nativeLinker(); final SymbolLookup stdLib = linker.defaultLookup(); final MemorySegment strlen_addr = stdLib.find("strlen").get(); // Define the descriptor // First parameter is Return, second onward is Arguments final FunctionDescriptor strlen_sig = FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS); // Call Handle final MethodHandle strlen = linker.downcallHandle(strlen_addr, strlen_sig); // Now Call final long len = (long)strlen.invokeExact(nativeString); System.out.println(STR . "Length of '\{ str }': \{ len }"); } catch (Throwable t) { t.printStackTrace(); } }
Ok, that wasn’t too much code. Let’s see what is going on here. Arena is auto-closable, so we start by putting it in a try so that we do not have to bother about closing it (Line #6). In this case, we are using single threaded application, we will call the ofConfined version of it. Since strlen takes a memory address (char *) for the string it wants to get the value of, we add that somewhere in native memory and Finget the address for it (Line #8). On Line #11 we will get the native linker. Remember this is the interface between native and Java code. On Line #13, we grab the address where currently strlen is loaded in memory.
Now that we know where the function is loaded, we will start with defining how the function looks like. This is done on Line #17. The first argument always defines the return value. Subsequent arguments will be what is accepted by this method.
We have all the information needed to prepare the call. On Line #20, we create a downcallHandle that will create a method handle for the linker to be able to successfully invoke this method.
When Java process calls a native function, we will create a downcallHandle. However, if a native process needs to callback to a Java function, we will create a upcallStub. We will see an example later in this blog.
Finally on Line #23, you will see a call to invoke this method and print on Line #24. Oh, that sounds fine, but what is it with that STR? Is that a typo?
Well not at all. While at this, why don’t we introduce one more small feature that is also silently being pushed as a preview feature.
Side Note – String Template
Java is also making displaying strings easier. STR. defines a string template processor. We will not go into the full depth of things, but what it can fill in is the variables enclosed in \{…}. That is the simplest kind of expression that it can process.
However, if we want to deal with the formatting aspects, e.g. only print a float to 2 decimal places, we will have to start looking at FMT template processor. For now we just used STR . processor.
A quick note when you are using something that uses a preview feature, the compilation becomes a bit different, else you will not be able to compile the code. For example, in this case, following are the command lines I have used to compile and run the program.
$ javac --enable-preview --release 21 NativeTest.java $ java --enable-preview NativeTest
This should help you compile/ run codes that use preview features.
Let’s see the output now,
INPUT: nt.runNative01_Len("Suvcodes rocks!") OUTPUT: Length of 'Suvcodes rocks!': 15
Comparing Strings (strcmp)
Hmm, can we do something else native that is not strlen? Maybe that was just made to work. What about strcmp? That takes two arguments, both char *, and returns a difference between second one and first. We will try to implement it.
/* * Calling String Compare */ public void runNative02_Compare(final String str1, final String str2) { try(final Arena arena = Arena.ofConfined()) { // Allocate off heap memory to store the string final MemorySegment nativeString1 = arena.allocateUtf8String(str1); final MemorySegment nativeString2 = arena.allocateUtf8String(str2); // Get Stdlib from Native Linker & get signature of String Length final Linker linker = Linker.nativeLinker(); final SymbolLookup stdLib = linker.defaultLookup(); final MemorySegment strcmp_addr = stdLib.find("strcmp").get(); // Define the descriptor // First parameter is Return, second onward is Arguments final FunctionDescriptor strcmp_sig = FunctionDescriptor.of(ValueLayout.JAVA_SHORT, ValueLayout.ADDRESS, ValueLayout.ADDRESS); // Call Handle final MethodHandle strcmp = linker.downcallHandle(strcmp_addr, strcmp_sig); // Now Call final short val = (short)strcmp.invokeExact(nativeString1, nativeString2); System.out.println(STR . "String Compare returned: \{ val }"); } catch (Throwable t) { t.printStackTrace(); } }
Nothing too different. let’s check the output of this one. When we use strcmp, it returns the ANSI string difference between the two strings being compared.
INPUT: runNative02_Compare("Java", "cJava"); OUTPUT: String Compare returned: -25
For the next few examples, we will write our own library in C. Since I am on a MacBook, I will just compile with clang. You can use whatever compiler you want.
C CODE STARTS BELOW – SKIP IF YOU WANT
Building the C library
We will build a simple and useless C library that we will use later to call from Foreign Function interface. We will use time libraries to just get current epoch, printable time etc. Let’s see how the header looks like.
Filename: libmytm.h
#ifndef libmytm_h_ #define libmytm_h_ extern char *get_time(); extern long int get_unix_epoch(); extern short compare_epoch(short (*callback)(long int, long int), long int ep1, long int ep2); #endif
Filename: libmytm.c
#include <time.h> #include <string.h> #include "libmytm.h" /* Return the formatted time */ char *get_time() { time_t ltime; time (<ime); return strtok(ctime(<ime), "\n"); } /* Get Unix Epoch */ long int get_unix_epoch() { time_t ltime; time(<ime); return (long int)ltime; } /* Compare two number (imagine Unix Epoch) */ short compare_epoch(short (*callback)(long int, long int), long int ep1, long int ep2) { short res = callback(ep1, ep2); return res; }
We are not talking about C programs here, so I will not describe what it does. I also wrote a program to test this library.
Filename: mytmtest.c
#include <stdio.h> #include "libmytm.h" short comp_epoch(long int a, long int b) { return b>=a?1:-1; } int main() { printf("%s\n", get_time()); printf("%ld\n", get_unix_epoch()); printf("%d\n", compare_epoch(comp_epoch, 1741950801, 1741950802)); return 0; }
Just to complete everything, I will also add the commands for compiling the library as well as running the test program I wrote.
-- Compile the library $ clang -arch arm64 -shared -undefined dynamic_lookup -o libmytm.so libmytm.c -- Compile the test program $ clang -L/library_path -arch arm64 -Wall -o test mytmtest.c -lmytm -- Run test $ ./test Sat Mar 15 15:07:10 2025 1742065630 1
C CODE ENDS
Now that we have a library in place, we can try out some other examples.
Executing Functions from Custom Library
Till now, we have been loading functions from libraries that are provided by C compiler. Now as a next step we will load a library, find a function and execute it. We will try to load our library and execute get_unix_epoch(). It just returns a long value and does not take any input.
/** * This is a test of just reading from our own library */ public void runNative03_Custom() { try(final Arena arena = Arena.ofConfined()) { // Load Custom Library final SymbolLookup custom = SymbolLookup.libraryLookup("/library_path/libmytm.so", arena); final MemorySegment memfntime = custom.find("get_unix_epoch").orElseThrow(); // Java method signature final FunctionDescriptor fndescriptor = FunctionDescriptor.of(ValueLayout.JAVA_LONG); final MethodHandle methfntime = Linker.nativeLinker().downcallHandle(memfntime, fndescriptor); // Now Call final long tm = (long)methfntime.invokeExact(); System.out.println(STR . "Current Unix Epoch time is \{tm}"); } catch (Throwable t) { t.printStackTrace(); } }
In this code, we are opening the library on Line #7 and within that library we are trying to get the memory address where the function of interest is loaded on Line #8. That’s the only thing that I had not done in the previous codes. Apart from that everything else is the same including defining the layout of the function, creating a linker and finally invoking it.
Output looks as follows.
INPUT: None OUTPUT: Current Unix Epoch time is 1742128323
One thing you will notice as soon as we start calling the custom library, we will start getting a Warning.
WARNING: A restricted method in java.lang.foreign.Linker has been called WARNING: java.lang.foreign.Linker::downcallHandle has been called by the unnamed module WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for this module
Return of Memory Address from Native
One of the challenges that we see when dealing with Memory Segment is very well defined in the documents. According to Oracle’s documentation,
The MemorySegment API uses zero-length memory segments to represent:
- pointers returned from a foreign function
- pointers passed by a foreign function to an upcall stub
- pointers read from a memory segment (more on that below)
This causes a big problem in reading the values. We will take a basic example, char *, or a C char array (string) as return type from a called function. A C string is always null (\0) terminated. This null character acts as an end marker for the string. Java strings on the other hand are not null terminated. The length has to be known when we deal with these strings.
If we try to cast MemorySegment to char array (char []) or java.lang.String, it does not work, as the length of string is not known. So, the answer is simple in that case, right? We write a custom code that goes over the contiguous memory address starting where Memory Segment starts, and stop where we get a null string. That should have worked nicely, if you had access to those memory address. If you try to get Memory Segment Address + 1, it will be unauthorized, and you will get ArrayIndexOutOfBounds exception.
Luckily, we have the solution written in documentation as well.
The returned zero-length memory segment cannot be accessed directly by the client: since the size of the segment is zero, any access operation would result in out-of-bounds access. Instead, the client must, unsafely, assign new spatial bounds to the zero-length memory segment. This can be done via the reinterpret (long) RESTRICTED method, as follows:
MemorySegment z = segment.get(ValueLayout.ADDRESS, ...); // size = 0
MemorySegment ptr = z.reinterpret(16); // size = 16
int x = ptr.getAtIndex(ValueLayout.JAVA_INT, 3); // ok
Let’s try a real example. We will call the formatted time API from our custom library that returns a char * (string address).
/** * This is a test of reading char * */ public void runNative04_Custom() { try(final Arena arena = Arena.ofConfined()) { // Load Custom Library final SymbolLookup custom = SymbolLookup.libraryLookup("/library_path/libmytm.so", arena); final MemorySegment memfntime = custom.find("get_time").orElseThrow(); // Java method signature final FunctionDescriptor fndescriptor = FunctionDescriptor.of(ValueLayout.ADDRESS); final MethodHandle methfntime = Linker.nativeLinker().downcallHandle(memfntime, fndescriptor); // Now Call, this memory segment returned has length 0 final MemorySegment tm = (MemorySegment)methfntime.invokeExact(); // Copy over this to a new segment final MemorySegment memstr = tm.reinterpret(Long.MAX_VALUE); final String tmstr = memstr.getUtf8String(0); System.out.println(STR . "Current time is \{tmstr}"); } catch (Throwable t) { t.printStackTrace(); } }
In this code, we are reinterpreting the returned value on Line #18. Ideally the function parameter should have been smaller long value instead of Long.MAX_VALUE, but assuming I do not know that length, I will just use the maximum.
This works and formatted time is nicely received by the Java program.
INPUT: None OUTPUT: Current time is Sun Mar 16 08:32:03 2025
Upcalling a Java function
This final example I will implement calling into a Java code from native. These are used for callback functions from native and are expected to have a function pointer passed. While building our C library, I have created a function that expects a function pointer for compare.
extern short compare_epoch(short (*callback)(long int, long int), long int ep1, long int ep2);
To call this function, let’s create a class that has the compare function we want to use. Just to make sure that our class is called, I will also put some print statements.
class EpochCompare { // Just static void printEpochTime(final long ep) { final LocalDateTime localDateTime = LocalDateTime.ofEpochSecond( ep, 0, OffsetDateTime.now().getOffset() ); System.out.println(STR . ">> Received Date: \{localDateTime}"); } static short epochCompare(final long ep1, final long ep2) { // Just to prove it did come to us... printEpochTime(ep1); printEpochTime(ep2); if (ep1 == ep2) return 0; return (short)(ep2>ep1?1:-1); } }
We will use epochCompare as the comparison function in this case. It gets two long values and returns 0 if equals. It returns +/- 1 based on which number is larger otherwise. Let’s try to see how the function will be called.
/** * This is a test of sending a JAVA function to C */ public void runNative05_Custom(final long ep1, final long ep2) { try(final Arena arena = Arena.ofConfined()) { // Native linker final Linker linker = Linker.nativeLinker(); // Create method handle for compare method final MethodHandle compareHandle = MethodHandles.lookup() .findStatic(EpochCompare.class, "epochCompare", MethodType.methodType(short.class, long.class, long.class) ); final FunctionDescriptor compFunctionDesc = FunctionDescriptor.of(ValueLayout.JAVA_SHORT, ValueLayout.JAVA_LONG, ValueLayout.JAVA_LONG); final MemorySegment compareFunc = linker.upcallStub(compareHandle, compFunctionDesc, arena); // Done creating method handle for the compare function // Load Custom Library final SymbolLookup custom = SymbolLookup.libraryLookup("/library_path//Users/suvendra/codes/java/VirtualThread/libmytm.so", arena); final MemorySegment memfntime = custom.find("compare_epoch").orElseThrow(); // Java method signature final FunctionDescriptor fndescriptor = FunctionDescriptor.of(ValueLayout.JAVA_SHORT, ValueLayout.ADDRESS, ValueLayout.JAVA_LONG, ValueLayout.JAVA_LONG); final MethodHandle methfntime = linker.downcallHandle(memfntime, fndescriptor); // Call final short retVal = (short)methfntime.invokeExact(compareFunc, ep1, ep2); System.out.println(STR . "Epoch Compare Value: \{retVal}"); } catch (Throwable t) { t.printStackTrace(); } }
Starting Line #11 we create the compare function. We lookup the static method handle from Java. On Line #18 we create a method descriptor and finally the upcallStub is created on Lin#19.
Rest of the process follows the same methods as the rest and we pass in this upcallStub to the memory address of the callee function. See Line #33 where we are sending the function pointer.
This in turn calls our Java function with the two long values, and finally returns result.
We have the input/ output below for couple of cases.
INPUT: runNative05_Custom(1742141794L, 1742141794L) OUTPUT: >> Received Date: 2025-03-16T12:16:34 >> Received Date: 2025-03-16T12:16:34 Epoch Compare Value: 0 INPUT: runNative05_Custom(1742141794L, 1742141822L) OUTPUT: >> Received Date: 2025-03-16T12:16:34 >> Received Date: 2025-03-16T12:17:02 Epoch Compare Value: 1
Conclusion
That’s all I have for today. There are a few variations in terms of example for the newly introduced foreign function interface. This new way of doing things makes it simpler for the developer to manage calling native functions. Hope you found this useful. Ciao for now!