JDK 26 Key Features — HTTP/3 Support and 15% G1 GC Throughput Boost

JDK 26’s Position

JDK 26, released on March 17, 2026, is a non-LTS release containing 10 JEPs. While the JEP count is lower than JDK 24 (24 JEPs), it includes practically impactful features: HTTP/3 support (JEP 517) and G1 GC throughput improvement (JEP 522). Additionally, Prepare to Make Final Mean Final (JEP 500) is noteworthy as it reveals Java’s long-term language direction.

HTTP/3 for HttpClient API (JEP 517, Final)

The HttpClient introduced in JDK 11 finally supports HTTP/3. HTTP/3 uses the QUIC protocol instead of TCP, offering benefits like Head-of-Line Blocking resolution (packet loss doesn’t affect other streams), faster connection establishment (0-RTT), and maintaining connections across network switches (Connection Migration).

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;

public class Http3Demo {
    public static void main(String[] args) throws Exception {
        // Create an HttpClient with HTTP/3 support
        HttpClient client = HttpClient.newBuilder()
            .version(HttpClient.Version.HTTP_3) // Specify HTTP/3
            .connectTimeout(Duration.ofSeconds(10))
            .build();

        // Create an HTTP/3 request
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("https://www.google.com"))
            .GET()
            .build();

        // Send request and receive response
        HttpResponse<String> response = client.send(
            request, HttpResponse.BodyHandlers.ofString()
        );

        System.out.println("=== HTTP/3 Response Info ===");
        System.out.println("Status code: " + response.statusCode());
        System.out.println("Protocol: " + response.version());
        System.out.println("Response size: " + response.body().length() + " chars");
        // Example output:
        // === HTTP/3 Response Info ===
        // Status code: 200
        // Protocol: HTTP_3
        // Response size: 14523 chars

        // Print headers
        response.headers().map().forEach((key, values) -> {
            if (key.startsWith("content") || key.startsWith("server")) {
                System.out.println(key + ": " + values);
            }
        });
    }
}

An important note when using HTTP/3: if the server doesn’t support HTTP/3, it automatically falls back to HTTP/2 or HTTP/1.1. Simply changing HttpClient.Version.HTTP_2 to HTTP_3 in existing code upgrades it, with no server compatibility concerns.

ProtocolTransport LayerKey Features
HTTP/1.1TCPSequential request/response
HTTP/2TCPMultiplexing, header compression, server push
HTTP/3QUIC (UDP)HoL Blocking resolved, 0-RTT, connection migration

G1 GC Throughput Improvement (JEP 522)

JDK 26 improves throughput of the default G1 GC by up to 15%. Since G1 has been the default GC since JDK 9 and is used by most Java applications, this improvement applies to virtually all projects without any configuration changes.

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class G1ThroughputDemo {
    public static void main(String[] args) {
        // G1 GC throughput test: simulating mass object allocation and deallocation
        // Run: java -verbose:gc G1ThroughputDemo

        Random random = new Random(42);
        List<byte[]> liveObjects = new ArrayList<>();
        int totalAllocated = 0;

        long start = System.nanoTime();

        for (int cycle = 0; cycle < 100; cycle++) {
            // Object allocation: create byte arrays of various sizes
            for (int i = 0; i < 1000; i++) {
                int size = random.nextInt(1024, 65536); // 1KB ~ 64KB
                liveObjects.add(new byte[size]);
                totalAllocated++;
            }

            // Object deallocation: remove 70% to trigger GC (Weak Generational Hypothesis simulation)
            int removeCount = (int) (liveObjects.size() * 0.7);
            for (int i = 0; i < removeCount; i++) {
                liveObjects.remove(liveObjects.size() - 1);
            }
        }

        long elapsed = (System.nanoTime() - start) / 1_000_000;

        System.out.println("=== G1 GC Throughput Test ===");
        System.out.println("Total objects allocated: " + totalAllocated);
        System.out.println("Surviving objects: " + liveObjects.size());
        System.out.println("Time elapsed: " + elapsed + "ms");
        System.out.println();
        System.out.println("JDK 26 G1 improvements:");
        System.out.println("  - Up to 15% throughput improvement");
        System.out.println("  - Remembered Set optimization");
        System.out.println("  - Write Barrier efficiency gains");
        // Example output:
        // === G1 GC Throughput Test ===
        // Total objects allocated: 100000
        // Surviving objects: 811
        // Time elapsed: 2340ms
        //
        // JDK 26 G1 improvements:
        //   - Up to 15% throughput improvement
        //   - Remembered Set optimization
        //   - Write Barrier efficiency gains
    }
}

The G1 throughput improvement was achieved through Remembered Set management and Write Barrier optimization. The Remembered Set is a data structure that tracks pointers from the Old generation to the Young generation. By reducing its management cost, application threads gain more CPU time.

AOT Object Caching with Any GC (JEP 516)

The AOT class loading introduced in JDK 24 (JEP 483) has taken another step forward. JDK 26 makes AOT object caching compatible with all GCs. Previously, AOT cache was only available with specific GCs, but now G1, ZGC, Shenandoah, Serial, and Parallel GCs all benefit from AOT caching.

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class AotCachingDemo {
    // AOT caching effect simulation
    // Run 1: java -XX:AOTCache=app.aotcache -XX:AOTMode=record AotCachingDemo
    // Run 2: java -XX:AOTCache=app.aotcache -XX:AOTMode=on AotCachingDemo

    public static void main(String[] args) {
        long startTime = System.nanoTime();

        // Typical application initialization code
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        String now = LocalDateTime.now().format(formatter);

        // Operations requiring class loading + linking
        var list = java.util.stream.IntStream.range(0, 1000)
            .boxed()
            .toList();

        long elapsedMs = (System.nanoTime() - startTime) / 1_000_000;

        System.out.println("=== AOT Object Caching Effect ===");
        System.out.println("Current time: " + now);
        System.out.println("Initialization time: " + elapsedMs + "ms");
        System.out.println("List size: " + list.size());
        System.out.println();
        System.out.println("JDK 26 AOT changes:");
        System.out.println("  - AOT cache available with all GCs");
        System.out.println("  - G1, ZGC, Shenandoah, Serial, Parallel all supported");
        System.out.println("  - Reduced class loading/linking cost on restart");
        // Example output:
        // === AOT Object Caching Effect ===
        // Current time: 2026-04-07 14:30:00
        // Initialization time: 45ms
        // List size: 1000
        //
        // JDK 26 AOT changes:
        //   - AOT cache available with all GCs
        //   - G1, ZGC, Shenandoah, Serial, Parallel all supported
        //   - Reduced class loading/linking cost on restart
    }
}

AOT caching is particularly effective in microservices environments. Since containers must re-load classes and go through JIT warmup on every restart, caching mitigates the Cold Start problem.

Other Notable JEPs

Prepare to Make Final Mean Final (JEP 500): The first step in a long-term project to strengthen the meaning of the final keyword. Currently, final fields can be modified through reflection or Unsafe, and this will be gradually blocked. Frameworks like Hibernate and Jackson use patterns that modify final fields via reflection, so library compatibility should be checked.

Applet API Complete Removal (JEP 504): The Applet API, which has existed since Java 1.0 in 1995, is completely removed. The java.applet package and related classes are deleted. Since it was already deprecated in JDK 9, virtually no projects should be affected. The official retirement of a 30-year legacy.

GC Evolution Timeline from JDK 23-26

The GC-related changes from JDK 23 to 26 show a clear direction:

VersionGC Change
JDK 23ZGC Generational mode becomes default
JDK 24ZGC Non-Generational mode removed, G1 Late Barrier Expansion
JDK 25Compact Object Headers finalized (heap 22% down), Shenandoah Generational finalized
JDK 26G1 throughput 15% up, AOT cache all GCs supported

Three axes — consolidation toward generational GC, object header compression, and G1 optimization — are progressing simultaneously, with JDK 26 bearing the fruits of this direction.

Practical Tips

FeatureStatusKey Points
HTTP/3FinalHttpClient.Version.HTTP_3, auto-fallback
G1 Throughput BoostFinalUp to 15% throughput improvement, no config changes needed
AOT Cache ExpansionFinalAOT object caching with all GCs
Final Keyword HardeningFinalGradual blocking of final field modification via reflection
Applet API RemovalFinaljava.applet package completely removed

The highest-impact change in JDK 26 is the G1 GC throughput improvement. Since most Java applications use G1 as their default, upgrading JDK alone yields performance improvements. HTTP/3 support also makes a meaningful difference in high-latency environments or mobile networks, so if your project uses HttpClient, it’s worth trying by changing just the version option.

Was this article helpful?