Skip to content

Java 22

Neelu edited this page Jul 26, 2026 · 1 revision

☕ Java 22: Foreign Function & Memory API Goes Standard

Release Date: March 19, 2024 | Type: Standard (non-LTS) | Status: ⚠️ End of Life


📋 Table of Contents

  1. Overview
  2. Core Features
  3. Best Practices

🎯 Overview

Java 22 standardizes the Foreign Function & Memory API (replacing JNI), previews unnamed variables/patterns for cleaner code, and adds Stream Gatherers — a way to write custom intermediate stream operations.

flowchart LR
    A["Java 17: FFM Incubator"] --> B["Java 18-20: refined previews"] --> C["Java 22: FFM STANDARD ✅"]
    D["JNI - complex, unsafe,\nboilerplate-heavy"] -->|replaced by| C
Loading

🚀 Core Features

1️⃣ Foreign Function & Memory API (Standard)

What: Call native (C) libraries and manage off-heap memory directly from Java — no JNI boilerplate, no native glue code compilation.

import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;

// Call the C standard library's strlen() function directly — no JNI!
try (Arena arena = Arena.ofConfined()) {
    Linker linker = Linker.nativeLinker();
    SymbolLookup stdlib = linker.defaultLookup();

    MethodHandle strlen = linker.downcallHandle(
        stdlib.find("strlen").orElseThrow(),
        FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS)
    );

    MemorySegment cString = arena.allocateUtf8String("Hello, Panama!");
    long length = (long) strlen.invoke(cString);
    System.out.println("Length: " + length); // 14
}

// Off-heap memory management — safer than sun.misc.Unsafe
try (Arena arena = Arena.ofConfined()) {
    MemorySegment segment = arena.allocate(1024); // 1KB off-heap
    segment.set(ValueLayout.JAVA_INT, 0, 42);
    int value = segment.get(ValueLayout.JAVA_INT, 0);
    System.out.println(value); // 42
} // memory automatically freed when Arena closes
flowchart TD
    A[Java Application] -->|"FFM API\n(safe, no glue code)"| B[Native C/C++ Library]
    A -.->|"Old way: JNI\n(complex, unsafe, boilerplate)"| B
    C[Arena] -->|manages lifecycle of| D[MemorySegment - off-heap memory]
    D -->|automatically freed| E[Arena.close]
Loading

Why it matters: This is a huge deal for performance-critical libraries (compression, crypto, ML inference) that need native code — no more hand-written JNI headers, no unsafe memory access, deterministic cleanup via Arena.


2️⃣ Unnamed Variables & Patterns (Preview)

What: Use _ for variables/pattern bindings you don't actually need — reduces noise and unused-variable warnings.

// Before: forced to name a variable you don't use
for (Order order : orders) {
    count++;  // order itself isn't used
}

// Java 22 Preview: unnamed variable
for (Order _ : orders) {
    count++;
}

// In pattern matching — ignore parts of a record you don't need
record Point3D(int x, int y, int z) {}

if (obj instanceof Point3D(var x, var y, var _)) {
    // z is discarded — we only care about x and y
    process(x, y);
}

// In catch blocks
try {
    riskyOperation();
} catch (IOException _) {
    // exception details irrelevant, just handle the failure case
    fallback();
}

3️⃣ Stream Gatherers (Preview)

What: A new Stream.gather() operation letting you write custom intermediate operations — something the Streams API famously couldn't do cleanly before (only Collectors handled custom terminal logic).

// Custom gatherer: sliding window of size 3
List<List<Integer>> windows = Stream.of(1, 2, 3, 4, 5)
    .gather(Gatherers.windowSliding(3))
    .toList();
// [[1,2,3], [2,3,4], [3,4,5]]

// Fixed-size batching — great for chunked API calls
List<List<String>> batches = ids.stream()
    .gather(Gatherers.windowFixed(100))
    .toList();
// Process 100 IDs at a time

// fold() — a stateful reduction that produces intermediate results, not just a final one
List<Integer> runningTotal = Stream.of(1, 2, 3, 4)
    .gather(Gatherers.fold(() -> 0, (acc, val) -> acc + val))
    .toList();
flowchart LR
    A[Stream: 1,2,3,4,5] -->|"gather(windowSliding(3))"| B["[1,2,3]"]
    A -->|window 2| C["[2,3,4]"]
    A -->|window 3| D["[3,4,5]"]
Loading

4️⃣ Statements Before super()/this() (Preview)

public class Order {
    private final double total;

    public Order(List<Item> items) {
        // Before Java 22: validation had to happen AFTER super(), awkwardly
        // Java 22 Preview: statements allowed before the constructor call
        if (items == null || items.isEmpty()) {
            throw new IllegalArgumentException("Items required");
        }
        super();
        this.total = items.stream().mapToDouble(Item::price).sum();
    }
}

5️⃣ Class-File API (Preview)

A standard API for parsing, generating, and transforming Java .class files — replacing the JDK's internal dependence on third-party bytecode libraries like ASM for tools that need it.


6️⃣ Launch Multi-File Source Programs

# Java 11 let you run a SINGLE .java file directly.
# Java 22 extends this to multiple files in one directory — still no compile step needed!
java src/Main.java
# Main.java can reference other .java files in the same directory,
# and they're compiled in-memory automatically

💡 Best Practices

✅ Migrate JNI-based native integrations to the FFM API — safer, no native glue code to maintain
✅ Use _ for genuinely unused loop variables, catch blocks, and pattern components — improves readability once out of preview
✅ Try Stream Gatherers for windowing/batching logic you'd otherwise hand-roll with imperative loops
❌ Don't rewrite stable, working JNI code just because FFM exists — migrate opportunistically, not urgently


Clone this wiki locally