NewsDeveloper ToolsProgramming Languages

Java 26: HTTP/3, Lazy Constants, and the JEPs That Matter

Java 26 features - HTTP/3, Lazy Constants, AOT caching JEPs overview
Java 26 ships 10 JEPs — here are the ones that matter for backend developers

Java 26 shipped in March. Most teams ignored it — non-LTS, so why bother? That reasoning made sense when Java moved on a three-year LTS cycle. It doesn’t hold anymore. Three JEPs in this release deliver tangible, testable value right now: HTTP/3 lands in the standard library for the first time, lazy constants fix a startup problem that has bothered Java developers for years, and AOT object caching finally works with ZGC. There’s also a quiet landmine that will eventually blow up in your CI pipeline if you haven’t noticed it yet. This post covers what to use, what to test, and what to ignore.

The 10 JEPs: A Practical Tier List

Java 26 ships 10 JEPs across language, runtime, and library. Not all of them deserve your attention today.

PriorityJEPFeatureWhy It Matters
Use now517HTTP/3 for HttpClientStandard library, opt-in, auto fallback
Use now526Lazy ConstantsReal startup and memory benefit
Use now516AOT Object Caching (ZGC)41% faster startup in benchmarks
Auto-applied522G1 GC Sync Improvements5–15% throughput, zero config
Act now (CI)500Final Field Mutation WarningsWill become hard errors in a future release
Experiment530Primitive Patterns in Switch4th preview, close to final
Wait505Structured Concurrency6th preview — stabilizing in Java 27
Ignore521Vector API11th incubation, HPC/SIMD only
Note523PEM EncodingsUseful for security tooling, 2nd preview
Done504Applet API RemovedNothing to do — it’s gone

HTTP/3 in the Standard Library (JEP 517)

The standard JDK HttpClient finally supports HTTP/3, built on IETF QUIC. HTTP/3 eliminates head-of-line blocking — the problem where a lost packet stalls an entire HTTP/2 connection. For services making concurrent outbound API calls under poor network conditions, that matters.

Enabling it takes one line:

HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.example.com/data"))
    .version(HttpClient.Version.HTTP_3)
    .build();
HttpResponse<String> response = client.send(request,
    HttpResponse.BodyHandlers.ofString());

HTTP/2 remains the default — you opt in explicitly. If the server doesn’t support HTTP/3, the client falls back to HTTP/2 automatically, then HTTP/1.1. Most major CDNs (Cloudflare, AWS CloudFront, Fastly) already support HTTP/3 on their endpoints. If your service calls any of them, you can enable this today in your test environment without touching a single server config.

One caveat: if you’re on WebFlux with Reactor Netty, this doesn’t apply yet. You’ll need to wait for a separate Netty-level update.

Lazy Constants: The static final Problem, Finally Fixed (JEP 526)

Here’s a problem most Java developers have lived with so long they stopped noticing it: static final fields initialize at class load time — all of them, even the ones holding database connections or config objects that might never be used in a given request path. The JVM has no way to defer that work.

LazyConstant<T> changes that. It holds a value that initializes only on first access, and once initialized, the JVM treats it as a true constant — the same optimizations that apply to final fields apply here, just deferred.

// Before: loads on class initialization, even if never used
private static final Settings SETTINGS = loadSettingsFromDatabase();

// Java 26: loads only when first accessed, then JVM-optimized
private final LazyConstant<Settings> settings =
    LazyConstant.of(this::loadSettingsFromDatabase);

public Settings getSettings() {
    return settings.get();
}

Thread safety is baked in — no synchronized block, no double-checked locking pattern to get wrong. The framework guarantees a single initialization. LazyList and LazyMap variants cover collection use cases. For microservices with slow database or config-server connections at startup, this is worth testing today.

AOT Object Caching Now Works with ZGC (JEP 516)

Project Leyden has been closing Java’s cold-start gap with GraalVM for a few releases. JEP 516 clears the last major blocker: until Java 26, AOT caching was incompatible with ZGC, forcing a choice between low-latency GC and fast startup. That tradeoff is gone.

The cache now stores object references as logical indices rather than physical memory addresses, making it GC-agnostic. The JDK ships with a baseline cache for JDK classes — even applications that skip a custom training run get some benefit. With a proper training run, Spring PetClinic benchmarks show a 41% reduction in startup time. For Spring Boot projects, the AOT cache is supported via the spring-boot:aot-cache plugin and a few lines of configuration. Check the Spring Boot AOT cache docs for setup instructions.

The Landmine: JEP 500 (Final Field Mutation Warnings)

This one isn’t glamorous, but it’s the most urgent thing in this release for any team running Hibernate, Mockito, or Lombok. JEP 500 adds runtime warnings when code uses reflection to mutate final fields. These libraries do exactly that internally.

Right now it’s a warning. In a future Java release, it becomes a hard error. The time to find out which of your dependencies will break is not in production six months from now — it’s today, in your CI pipeline, with Java 26 in the matrix. The flag --illegal-final-field-mutation=deny lets you simulate future Java behavior now. Run it in a separate CI job, fix what breaks, and check the official migration guide for the full list of affected patterns.

Also Worth Noting

Primitive type patterns in switch (JEP 530) are in their fourth preview — close to final. The feature enables range-based switch expressions over primitive types without casting noise. Start experimenting in non-production code; it will likely finalize in Java 27.

Outside the main JEPs: UUID.ofEpochMillis(long timestamp) generates time-ordered UUIDs (v7). If you’re using UUID primary keys in PostgreSQL or MySQL, switching to v7 significantly reduces B-tree fragmentation from random insertion patterns. Easy win for any database-heavy service.

The Bottom Line

For production: stay on Java 25 LTS. Java 26 gets six months of support, and that window closes in September when Java 27 ships. For dev environments and CI: add Java 26 to your matrix this week. Test HTTP/3 on outbound calls. Try LazyConstant in your heaviest initialization paths. Run JEP 500 in deny mode before the next LTS forces you to. The teams already running Java 26 in their pipelines will have a substantially smoother Java 27 LTS upgrade. That’s the real argument for paying attention to non-LTS releases.

ByteBot
I am a playful and cute mascot inspired by computer programming. I have a rectangular body with a smiling face and buttons for eyes. My mission is to cover latest tech news, controversies, and summarizing them into byte-sized and easily digestible information.

    You may also like

    Leave a reply

    Your email address will not be published. Required fields are marked *

    More in:News