Introduction
In this post I will show you the basics of project Panama and working with foreign functions and Memory. This will be the basis for the next posts. Each one building on the previous one. The goal of the series is to make you proficient with the API, but to also learn you some performance tips and tricks
What is Project Panama?
Project Panama is a new API that allows you to call native code from Java. Meaning you can use it to call C functions from Java. We already have JNI, but the new API is much friendlier and easier to use, as you will see. The performance of Panama is on par or better than JNI. The foreign function and Memory API were added as part of Project Panama and delivered with JEP-454 as was part of JDK 22.
Why use Panama
Java has a great ecosystem of libraries and frameworks. Maven Central is full of libraries that can solve many challenges. But C has a great ecosystem of libraries as well! The advantage of C is that it has many libraries in areas where Java is a bit underrepresented. Take AI, Game Development, and your operating system. One example would be IO_Uring an async IO library for Linux. We could use this library from Java. Or we could use Tensorflow if we wanted to do Machine Learning. All this is possible with the foreign function API using pure Java (if you don't count the C library itself).
Hello, Panama!
So let's get started with the Hello World version of Panama. The idea is to print a string to the console using the C printf function. The idea is to allocate some memory that holds a String and then call the C function. To do that, we need a couple of things, so let’s start with that.
The first thing we need is a C function that takes a String as an argument and prints it to the console. For this
I chose the printf function. The signature of the function is
int printf ( const char * format, ... );. So let's dissect this signature as we need it later. The signature is
basically these three things:
- The function name
- The return type
- The arguments
Now that we know this, we can prepare what we need to call the C function. We need to allocate some memory that holds
the string we want to print. For this we will use the Arena. The Arena is a class that can be used to allocate
memory. We need to find the symbol of the printf function in the C library. We can do that using the native Linker.
The last thing we need is the FunctionDescriptor.
So like I said, the first thing we need is an Arena to manage the native memory. In the next example the Arena is
created inside a try-with-resources statement. There are four types of Arena's but let's keep things simple for
now. I used the ofConfined arena because it is meant for single threaded use with a clear end.
| |
You don't need to use the Try and call close yourself if you want. The Arena will manage the memory for you inside
the try and when it closes it will free the memory. This will help you avoid memory leaks. Inside the try we want to
allocate native memory that can hold the String we want to print. The Arena has some helper methods that make this
easy to do like the allocateFrom(). It takes a String as a Parameter and returns a MemorySegment backed by native
memory.
| |
A MemorySegment is a reference to a block of memory. A MemorySegment can be backed by native memory or Java heap memory.
In
this case we allocated some native memory. The MemorySegment holds some metadata about the memory like the size and
the address.
Now we need to make the call to the native method (a downcall). First let’s find the symbol of the printf function
using the Linker.
| |
The Linker is aware of the most common/popular C libraries available on the system. So we can use the defaultLookup
method to find the symbol. The findOrThrow method will throw an exception if the symbol is not found. The
MemorySegment that is returned holds the address of the symbol.
Next up is the FunctionDescriptor. The FunctionDescriptor is used to describe the signature of the function we want
to call. The FunctionDescriptor takes as parameter the return type and the arguments. In this case we want to call
the printf function which returns an int and takes a char* as an argument. So we can create the FunctionDescriptor
like this:
| |
The ValueLayout is a class that describes the type of the argument. In this case we want to pass a char* so we use
the ADDRESS layout. Because pointers in C are just an address.
Combining all of this, we can create a MethodHandle that can be used to call the printf function. This looks
as follows:
| |
Now all we need to do is call the printf method with the memorySegment that holds the string we want to print.
| |
And that's it! This will invoke the C method and print the string to the console. The value we get back from the function is the number of characters printed.
Complete Example
The complete example is shown below:
| |
This is the complete example of this post. If you followed along it should look like this.
Conclusion
In this first part of the Panama series, we've covered the fundamentals of calling native C functions from Java. We've seen
how to allocate native memory using Arena, look up C function symbols with Linker, describe function signatures with
FunctionDescriptor, and finally make the actual downcall using a MethodHandle.
While our “Hello, Panama!” example is simple, it demonstrates the core concepts you'll use throughout the series. The beauty of the Foreign Function & Memory API is that it gives you direct, type-safe access to native code without the complexity and boilerplate of JNI.
In the next post, we'll build on this foundation by exploring how to work with C structs and primitive types from Java. We'll see how to map complex C data structures to Java and pass them back and forth between the two worlds. This will give you the tools to work with more realistic C libraries that use structured data.