Skip to main content
Guides Menu

Java 26 New Features

Deep dive into Java 26 (JDK 26) new features. Learn about HTTP/3, Lazy Constants, and Structured Concurrency with real-world examples and internal concepts.

Vishal Hulawale
April 2026·15 min read

"Java 26 marks a turning point in the platform's performance story, bridging the gap between network modernization and internal runtime efficiency."

The release of JDK 26 on March 17, 2026, brought ten JDK Enhancement Proposals (JEPs) that solve long-standing friction points for developers. From the way we initialize expensive objects to how we handle concurrent tasks, Java 26 is about making the language more robust and the runtime faster.

HTTP/3 for the HTTP Client API

JEP 517

🔹 Problem

Modern web applications suffer from "Head-of-Line Blocking" at the transport layer. In HTTP/2, a single lost packet on a TCP connection stalls all streams, even if the other packets arrived safely. This increases latency on mobile networks and unstable connections.

✅ Solution

Java 26 adds native support for HTTP/3 to the java.net.http.HttpClient. By using the UDP-based QUIC protocol, it enables independent streams where one lost packet only affects its specific stream, not the entire connection.

⚙️ How It Works

The implementation is transparent and opt-in. When you specify Version.HTTP_3, the client uses a QUIC transport. If the server doesn't support it or UDP is blocked by a firewall, it gracefully downgrades to HTTP/2.
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

// 1. Create a client that prefers HTTP/3
try (HttpClient client = HttpClient.newBuilder()
        .version(HttpClient.Version.HTTP_3)
        .build()) {

    HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("https://openjdk.org"))
            .GET()
            .build();

    // 2. The client negotiates QUIC automatically
    HttpResponse<String> response = client.send(
        request, HttpResponse.BodyHandlers.ofString());

    System.out.println("Actual Protocol: " + response.version());
}

Lazy Constants (Stable Values)

JEP 526

🔹 Problem

Initializing expensive objects safely in a multi-threaded environment usually requires "Holder Classes" or "Double-Checked Locking." These patterns are boilerplate-heavy and, more importantly, the JVM cannot optimize the resulting fields as true constants because they are not static final at initialization time.

✅ Solution

Introduces java.lang.LazyConstant<T>. It provides a clean API for deferred, one-time initialization that is natively understood by the JVM's JIT compiler.

⚙️ How It Works

Once a LazyConstant is computed, the JIT compiler can perform "constant folding." This means it treats the value as if it were a literal constant, potentially inlining it directly into machine code for maximum speed.
import java.lang.LazyConstant;

public class AppConfig {
    // Defines an uninitialized constant
    private static final LazyConstant<DatabaseClient> DB = 
        LazyConstant.of(() -> new DatabaseClient("prod-url"));

    public void start() {
        // Initialization happens exactly once on the first .get()
        // Subsequent calls are as fast as reading a final field
        DB.get().connect();
    }
}

Structured Concurrency

JEP 525

🔹 Problem

Unstructured concurrency (using ExecutorService) leads to "orphaned threads." If a main task fails, its subtasks often keep running blindly, wasting resources and making debugging a nightmare because stack traces don't show the relationship between parent and child tasks.

✅ Solution

Java 26 refines StructuredTaskScope (6th Preview). It treats groups of related tasks as a single unit. If the scope closes, all subtasks are guaranteed to be terminated.

⚙️ How It Works

The scope creates a strict hierarchy. In Java 26, a new onTimeout()callback was added, allowing custom joiners to handle partial results when a time limit is reached, rather than just throwing an exception.
import java.util.concurrent.StructuredTaskScope;

try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
    // Fork subtasks
    var userTask = scope.fork(() -> fetchUser(id));
    var orderTask = scope.fork(() -> fetchOrders(id));

    scope.join();           // Wait for both
    scope.throwIfFailed();  // Propagate any error from subtasks

    // If one fails, the other is automatically cancelled
    return new Dashboard(userTask.get(), orderTask.get());
}

Primitive Types in Patterns

JEP 530

🔹 Problem

Pattern matching in Java previously only worked with Reference types (Objects). If you wanted to check if an int was actually abyte, you had to manually cast and check ranges, which is error-prone and verbose.

✅ Solution

Allows primitive types in instanceof andswitch patterns. It provides safe, lossless conversion and binding in one step.

⚙️ How It Works

The compiler performs a "safe conversion" check. A pattern match only succeeds if the source value can be converted to the target primitive type without losing any information (no truncation).
public void process(int value) {
    // Safe binding: only matches if value fits in a byte
    if (value instanceof byte b) {
        System.out.println("It's a small number: " + b);
    }

    // Switch on doubles and floats (new in Java 26!)
    double d = 42.0;
    switch (d) {
        case 0.0 -> System.out.println("Zero");
        case double val when val > 0 -> System.out.println("Positive");
        default -> System.out.println("Negative");
    }
}

G1 GC: Contention Reduction

JEP 522

🔹 Problem

The G1 Garbage Collector used to suffer from synchronization contention between application threads (performing writes) and GC threads (managing the card table). On large, write-heavy heaps, this contention caused "jitter" and reduced total throughput.

✅ Solution

Introduces a "Dual Card Table" architecture. By separating the refinement and application write tracking, the JVM significantly reduces the need for threads to synchronize on the same memory addresses.

⚙️ How It Works

This is an internal architectural change. In x64 environments, the write barrier instructions were reduced from ~50 to just 12, resulting in a 5-15% throughput gain for write-intensive applications like large caches.
// No code changes needed! Just run on JDK 26
// Throughput gains are automatic for G1
java -XX:+UseG1GC -Xmx16g -jar my-big-app.jar

Other notable removals and additions

  • PEM API (JEP 524): Finally, a standard way to read .pem certificates without BouncyCastle.
  • Applet Removal (JEP 504): The java.applet package is officially gone.
  • AOT for ZGC (JEP 516): Ahead-of-Time object caching now works with the low-latency ZGC.

Related Guides