Java 11-26 Core Library Evolution: HTTP/3, FFM, Stream Gatherers & Practical Upgrades
This article explores major Java core library enhancements from versions 11 to 26, including the HTTP Client with HTTP/3 support, FFM API for safe native interop replacing JNI, Stream Gatherers for custom intermediate operations like sliding windows, SequencedCollection for unified ordered collections, KDF and post-quantum cryptography, plus numerous String, IO, and random-number improvements that reduce third-party dependencies.
Why JNI Crashed My JVM
The author recounts integrating a hardware encryption module that only provided a C .so library. Using JNI required writing C headers, Java native declarations, generating JNI headers with javah, implementing C code, compiling to .so, and placing it on java.library.path. A pointer overflow in the C library crashed the entire JVM with no usable stack trace because JNI shares the same process space as the JVM.
Switching to JNA eliminated C code but cut performance by 30–50% and introduced complex struct-mapping issues. Java 22's FFM API (JEP 454) finally enabled pure-Java calls to C libraries with near-JNI performance and, crucially, memory-safety guarantees: out-of-bounds access throws a Java exception instead of crashing the JVM.
New HTTP Client: Replacing HttpURLConnection
Why HttpURLConnection Belongs in a Museum
Only supports HTTP/1.1 — no HTTP/2 multiplexing
Awkward API requiring manual InputStream / OutputStream management
No async support — concurrent requests need a custom thread pool
No WebSocket support
Modern HTTP Client API (Java 11+, JEP 321)
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.build();
// Synchronous request
HttpResponse<String> response = client.send(
HttpRequest.newBuilder(URI.create("https://api.example.com/data"))
.header("Accept", "application/json")
.GET()
.build(),
HttpResponse.BodyHandlers.ofString());
// Asynchronous request (returns CompletableFuture)
CompletableFuture<HttpResponse<String>> future = client.sendAsync(
HttpRequest.newBuilder(URI.create("https://api.example.com/data")).GET().build(),
HttpResponse.BodyHandlers.ofString());Builder-pattern API, synchronous and asynchronous modes, built-in HTTP/2.
HTTP/3 Support in Java 26 (JEP 517)
HTTP/3 runs on QUIC, eliminating HTTP/2 head-of-line blocking. JDK 26's HTTP Client supports HTTP/3 natively — no third-party library needed for latency-sensitive services (real-time gaming, high-frequency trading).
Gotcha: sendAsync uses an internal platform-thread Executor by default. If you're on JDK 21+ with virtual threads, supply a virtual-thread executor: <code>HttpClient client = HttpClient.newBuilder() .executor(Executors.newVirtualThreadPerTaskExecutor()) // JDK 21+ .build();</code>
FFM API: Safe Native Interop Without JNI
Core Concepts
Arena : Memory-management scope; exiting the arena auto-releases memory, preventing leaks
MemorySegment : Abstracts a memory region (heap or off-heap)
Linker : Maps Java methods to C functions
ValueLayout : Describes C-to-Java type correspondences
Practical Example: Calling strlen from C Standard Library
// Locate C standard library's strlen
Linker linker = Linker.nativeLinker();
SymbolLookup stdlib = linker.defaultLookup();
MethodHandle strlen = linker.downcallHandle(
stdlib.find("strlen").orElseThrow(),
FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS));
// Allocate C string in an Arena — automatic lifecycle management
try (Arena arena = Arena.ofConfined()) {
MemorySegment cString = arena.allocateUtf8String("Hello FFM!");
long len = (long) strlen.invoke(cString);
System.out.println(len); // Output: 10
}
// Arena closes, memory auto-released — no leaksThe try-with-resources block ensures C memory is freed on scope exit — no manual free() calls.
Comparison: JNI vs JNA vs FFM
Development Efficiency : JNI low (write C code), JNA high, FFM high (pure Java)
Performance : JNI high, JNA medium (reflection overhead), FFM high (near JNI)
Safety : JNI low (C crash kills JVM), JNA low (same), FFM high (bounds-checked)
Type Safety : JNI none, JNA weak, FFM strong (explicit ValueLayout)
FFM isolates C unsafety: even if the C library has bugs, FFM's bounds checks throw Java exceptions instead of crashing the JVM.
Limitation: FFM currently supports only C (name-mangling issues), not C++. C++ libraries need an extern "C" wrapper.
Stream Gatherers: Completing the Stream API (Java 24, JEP 485)
The Pain Point
Stream intermediate operations were fixed ( filter, map, sorted, distinct, limit, skip). Custom operations like sliding windows, deduplication keeping the last element, or group transformations required custom Collector s or awkward flatMap hacks.
Gatherer Mechanics
A Gatherer defines four functions:
initializer : Creates initial state (e.g., a window list)
integrator : Processes each element, decides to emit, accumulate, or drop
combiner : Merges parallel results (optional)
finisher : Handles remaining state at stream end (optional)
Built-in Gatherers
// windowFixed: fixed-size windows
List<List<Integer>> windows = Stream.of(1,2,3,4,5,6)
.gather(Gatherers.windowFixed(3))
.toList();
// Result: [[1,2,3], [4,5,6]]
// windowSliding: sliding windows
List<List<Integer>> sliding = Stream.of(1,2,3,4,5)
.gather(Gatherers.windowSliding(3))
.toList();
// Result: [[1,2,3], [2,3,4], [3,4,5]]
// fold: custom accumulation
Integer sum = Stream.of(1,2,3,4,5)
.gather(Gatherers.fold(() -> 0, (acc, n) -> acc + n))
.findFirst()
.orElse(0);Custom Gatherer: Sliding-Window Moving Average
Gatherer<Integer, ?, Double> movingAverage(int windowSize) {
return Gatherer.ofSequential(
() -> new ArrayDeque<Integer>(), // initial state: a queue
(window, element, downstream) -> {
window.addLast(element);
if (window.size() > windowSize) {
window.removeFirst(); // drop oldest when full
}
if (window.size() == windowSize) {
double avg = window.stream()
.mapToInt(Integer::intValue)
.average()
.orElse(0);
downstream.push(avg); // emit average
}
return true; // continue
});
}
List<Double> avgs = Stream.of(10,20,30,40,50)
.gather(movingAverage(3))
.toList();
// Result: [20.0, 30.0, 40.0]Gatherers turn Stream API from a "half-finished" tool into a complete toolkit — complex stream operations that previously required RxJava/Reactor now work in the standard library.
Gotcha: Gatherers require Java 24+. Projects on JDK 21 must wait to upgrade.
SequencedCollection: Unified Ordered-Collection Interface (Java 21, JEP 431)
The Problem
Listlacked getFirst() / getLast() — you wrote list.get(0) and
list.get(list.size()-1)Dequehad getFirst() / getLast() but List didn't LinkedHashSet has order, but Set interface doesn't guarantee it
New Interfaces
SequencedCollection : Ordered collection with addFirst / getFirst / removeFirst and addLast / getLast / removeLast SequencedSet : Ordered Set, extends SequencedCollection SequencedMap : Ordered Map with firstEntry / lastEntry / pollFirstEntry, etc. List, Deque, LinkedHashSet all implement SequencedCollection — one uniform API for first/last operations:
SequencedCollection<String> seq = new ArrayList<>();
seq.addLast("b"); // equivalent to add("b")
seq.addFirst("a"); // prepend
String first = seq.getFirst(); // get first
String last = seq.getLast(); // get lastSmall change, but readability improves significantly.
Security Enhancements: KDF & Post-Quantum Crypto
Key Derivation Function API (Java 25, JEP 510)
Standardizes HKDF, PBKDF2, etc. — no more third-party implementations or hand-rolled code:
KDF hkdf = KDF.getInstance("HKDF-SHA256");
SecretKey key = hkdf.deriveKey("AES", password, salt, 32);Cross-vendor compatibility; avoids lock-in to a specific provider.
Post-Quantum Algorithms (Java 24, JEP 496/497)
Quantum computers threaten RSA/ECC via Shor's algorithm. Java 24 introduces two lattice-based algorithms:
ML-KEM (Module-Lattice-Based Key Encapsulation Mechanism): Key encapsulation, replaces RSA/ECC key exchange
ML-DSA (Module-Lattice-Based Digital Signature Algorithm): Digital signatures, replaces RSA/ECDSA
Both are considered quantum-resistant. Systems requiring long-term confidentiality (certificates, financial data) should plan migration now.
Other Practical Improvements
String & IO Enhancements
Java 11 : isBlank(), lines(), strip(), repeat() — no more Apache Commons Lang for basic string ops
Java 11 : Files.readString(), Files.writeString(), Path.of() — one-liner file read/write
Java 18 (JEP 400): Default UTF-8 everywhere. Previously Windows defaulted to GBK, Linux to UTF-8 — cross-platform encoding bugs eliminated
Java 16 (JEP 380): Unix-domain socket channels — faster, safer local IPC than TCP loopback
PRNG Enhancements (Java 17, JEP 356)
RandomGeneratorinterface unifies all generators SplittableRandom: Forkable generator, better parallel-stream performance
New algorithms like Xoshiro256PlusPlus: longer period, better statistical properties
For simulations, games, crypto — stop using legacy Random (synchronized, poor performance).
Simple Web Server (Java 18, JEP 408)
jwebserver -p 8080 -d ./staticOne-command static file server — handy for frontend debugging, static asset preview. Simpler than Python's http.server or installing nginx, and it's built into the JDK.
Summary of Java 11–26 Core Library Wins
HTTP Client: HTTP/2 → HTTP/3, no third-party HTTP libs needed
FFM API: Replaces JNI, safe native calls, JVM no longer crashes from C bugs
Stream Gatherers: Completes Stream API — sliding windows, custom intermediate ops
SequencedCollection: Unified ordered-collection interface
KDF & post-quantum crypto: Long-term security readiness
String/IO micro-improvements: Cut third-party dependencies in half
Individually these features aren't "sexy," but together the standard library now covers most daily needs — many scenarios that once required external dependencies now work out of the box, slashing dependency count and easing upgrades.
Three Immediate Actions
Still on HttpURLConnection? Pick a small module, try the new HTTP Client. Convert a sync call and feel the relief of not manually closing InputStream.
Calling .so / .dll via JNI? Evaluate rewriting with FFM — especially modules that have crashed the JVM. FFM's memory safety justifies the effort.
Complex Stream ops in your codebase? If you're on JDK 24+ and have sliding-window or custom group-transform needs, rewrite with Gatherers for cleaner code.
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.
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.
