Skip to content
Java 11-26 Core Library Evolution: HTTP/3, FFM, Stream Gatherers & Practical Upgrades

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.

Tinker Programmer
Tinker Programmer
Tinker Programmer

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 leaks

The 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

List

lacked getFirst() / getLast() — you wrote list.get(0) and

list.get(list.size()-1)
Deque

had 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 last

Small 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)

RandomGenerator

interface 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 ./static

One-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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaHTTP/3JNIHTTP ClientStream GatherersPost-Quantum CryptographyFFM APISequencedCollection
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.