Why Migrate to Java 21
Every mission-critical Java program needs to migrate to Java 21 as soon as possible. I'll explain why in this article.
When I asked Claude the same question, it gave me the following list of benefits:
Yes, instead of regular threads, you can now scale your async programming without much complexity. Even though Virtual Threads are the top feature in Java 21, they might not be as relevant to you if your current threads manage your workload pretty well. That's why, along with 100 other useful features and performance improvements, I want to highlight why one feature can be game-changing for your enterprise Java applications.
Project PANAMA
What is Project Panama?
Project Panama is an OpenJDK initiative launched to improve the connection between Java and non-Java APIs. Well, if that doesn't make sense to you, let's dive deep into understanding what non-Java APIs Java uses to make life easier for your applications.
The two main giant operating systems we know—Windows and Linux—are both primarily built on C and C++ at their core because these languages provide the performance, control, and hardware access that operating systems require. Let's move back to java to understand how java is interacting with these operating systems to handle native system calls.
To explain this I'll take a real world example that I faced recently. Let's assume your enterprise Java application needs to write an extensive number of files into the file system.
As we all know, Java is a platform-independent language that runs on the JVM. However, I/O operations (reading files, network communication, console input/output) require direct interaction with the operating system, which is platform-specific.
┌─────────────────────────────────────┐
│ Java Application Code │
│ (Platform Independent) │
└─────────────────────────────────────┘
↓ How to connect?
┌─────────────────────────────────────┐
│ Operating System │
│ (Windows, Linux, macOS) │
│ (Platform Specific) │
└─────────────────────────────────────┘
Java code can't directly call OS functions because:
That's why Java uses the Java Native Interface (JNI) as the bridge. That's where all the magic happens, at least before Java 21.
The JNI Bridge Architecture
Java Layer (Your Code)
↓
Java Standard Library (java.io, java.net)
↓
JNI Layer (native keyword methods)
↓
Native C/C++ Implementation
↓
Operating System Calls
↓
Hardware (Disk, Network Card, etc.)
Traditional JNI Overhead
Every I/O operation involves:
Recommended by LinkedIn
1. Java method call
2. JNI boundary crossing (expensive!)
3. Parameter marshalling (convert Java types to C types)
4. Native C function execution
5. OS system call
6. Return value marshalling (convert C types back to Java)
7. JNI boundary crossing (expensive!)
8. Return to Java
The JNI boundary crossing is slow because:
Since our example scenario is related to Java File I/O, I'm specifically talking about the File I/O related things only, but the principal remains same for all other Java I/O retaliated activities. I'm certain that after explaining JNI, you'll understand where I'm going with this explanation. So let's deep dive into the real world problem that we need to fix.
Problem: Need to improve the file generation time significantly.
After a few attempts at logic refactoring and spending weeks, I managed to improve the file generation time by only 2%–3%. Then I felt that I was going in the wrong direction. That's where things started to change. While deep diving into the debug logs, I was able to understand where the actual latency was coming from. IIt's not the logic, and it's not the JVM memory. The latency is coming from the JNI layer while wrapping the Java code into C++ to write the files.
Now it's pretty clear that, to see any significant improvement, I need to change the JNI layer of Java. While researching that, I found an interesting topic: Project Panama. Lucky me, because Project Panama is now officially part of Java 21.
Foreign Function & Memory API (FFM) - Call native libraries without JNI
The FFM API Solution
With Java 21's Foreign Function & Memory API you can simply call Operating system functionality without any wrapper classes:
import java.lang.foreign.*;
public class ModernIO {
private static final Linker LINKER = Linker.nativeLinker();
private static final SymbolLookup STDLIB = LINKER.defaultLookup();
public static void readFileDirect(String path) throws Throwable {
// Find the open() function
MemorySegment openFunc = STDLIB.find("open").orElseThrow();
// Define function signature
FunctionDescriptor openDesc = FunctionDescriptor.of(
ValueLayout.JAVA_INT, // returns int (file descriptor)
ValueLayout.ADDRESS, // const char *pathname
ValueLayout.JAVA_INT // int flags
);
// Create method handle
MethodHandle open = LINKER.downcallHandle(openFunc, openDesc);
try (Arena arena = Arena.ofConfined()) {
// Allocate native string
MemorySegment pathStr = arena.allocateUtf8String(path);
// Call open() directly - NO JNI!
int fd = (int) open.invoke(pathStr, 0); // O_RDONLY = 0
if (fd >= 0) {
System.out.println("File opened with fd: " + fd);
// Similarly can call read() and close() directly
// 5-10x faster than traditional JNI!
}
}
}
}
Performance Comparison
Why This Matters
Key Takeaway
Every time you do I/O in Java (files, network, console), you're making native system calls through JNI, whether you realize it or not!
Java 21's FFM API is revolutionizing this by providing a faster, safer, and more modern way to interact with native code and perform I/O operations.
I hope you learn new things from this article. Thanks for reading and happy coding! Cheers!