Java 27: The Release Where Good Defaults Win

JDK 27 shipped September 15, 2026 with 9 JEPs. Four are final — and two of them change how every JVM you run behaves without touching a line of code.

JDK 27 went GA on September 15, 2026 (build 35), and it’s a short-term release with a clear theme: stop asking developers to opt in to good defaults, and just turn them on. Nine JEPs made the cut — four final, four previews, one incubator.

If you only take one thing from this post: two of the finals move under your existing applications. You’ll get them whether you ask or not.

The four finals

1. G1 is now the default GC everywhere (JEP 523)

Until now, HotSpot quietly picked the Serial collector when the heap was under ~2 GB or the machine had a single CPU. That meant plenty of containerized apps were running on a single-threaded collector without anyone realizing it. In 27, G1 is the default in all environments — the Serial fallback is gone.

Nothing to change in your code. But if you ever tuned around Serial’s behavior in small containers, re-measure: pause characteristics will differ.

2. Compact object headers by default (JEP 534)

Every Java object carries a header; the classic layout is 96 bits. Compact headers shrink it to 64 bits, cutting heap usage by around 22% in SPECjbb2015 benchmarks while holding or improving CPU time. The feature was finalized as opt-in back in JDK 25 — Amazon and SAP (in SapMachine) have been running it in production. Now it’s the default, and the layout even reserves 4 bits for Project Valhalla’s future value objects.

To go back to the classic layout:

java -XX:-UseCompactObjectHeaders -jar app.jar
Classic 96-bit object header versus compact 64-bit header layout
96 bits → 64 bits: the header every object carries.
Interactive demo · header memory explorer
Classic (96-bit)
114 MB
Compact (64-bit)
76 MB
Compact headers save 38 MB of header memory here — roughly a third of the header footprint, before counting any payload.

3. Post-quantum hybrid key exchange for TLS 1.3 (JEP 527)

The threat model is “harvest now, decrypt later”: adversaries record encrypted traffic today and decrypt it once large-scale quantum computers arrive. JDK 27 implements hybrid key exchange (RFC 9954) combining classical ECDHE with NIST’s ML-KEM algorithm, so the handshake stays secure as long as either component holds.

Three hybrid named groups ship — X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024 — with X25519MLKEM768 first in the default list. Apps using javax.net.ssl pick them up with no code changes.

4. JFR stops writing your secrets to disk (JEP 536)

Flight Recorder had a quiet leak: recordings captured initial environment variables, system properties, and command-line arguments — which is exactly where JDBC passwords, API keys, and cloud credentials live. Any .jfr file attached to a support ticket was a potential secret exposure.

In 27, sensitive values are redacted in-process before they’re written to the recording. You can extend coverage with your own keys:

java -XX:FlightRecorderOptions:redact-key=+STRIPE_SECRET,+DB_PASS -jar app.jar

This is the secure-by-default change I like most in the release: it prevents incidents instead of responding to them.

The four previews (worth tracking, not shipping)

Lazy Constants, 3rd preview (JEP 531)

Formerly “Stable Values.” A LazyConstant<T> computes its value on first access, exactly once, thread-safely — and the JVM can treat it as a constant-folding target like a final field, without eager initialization:

import java.lang.LazyConstant;

public class ConfigurationProvider {
    private static final LazyConstant<ServerConfig> CONFIG =
            LazyConstant.of(() -> loadConfigFromRemoteVault());

    public static ServerConfig get() {
        return CONFIG.get();
    }
}

This round trims the API to just get() (the low-level isInitialized() and orElse() are gone) and adds Set.ofLazy(...) alongside the existing List.ofLazy and Map.ofLazy factories. Requires --enable-preview, as always.

Primitive types in patterns, 5th preview (JEP 532)

switch, instanceof, and patterns finally accept primitives directly:

static String classify(Object value) {
    return switch (value) {
        case int i when i < 0 -> "negative int: " + i;
        case int i            -> "non-negative int: " + i;
        case double d         -> "a double: " + d;
        default               -> "something else";
    };
}

Five previews in, the API shape is stable — recent rounds have been about the compiler’s dominance checks and exactness rules, catching ambiguous or lossy bindings at compile time. Finalization looks close.

Structured Concurrency, 7th preview (JEP 533)

The Project Loom workhorse keeps converging: subtasks coordinated in a lexical scope, so nothing leaks and nothing gets orphaned:

try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
    var inventory = scope.fork(() -> inventoryClient.checkStock(orderId));
    var payment   = scope.fork(() -> paymentClient.authorize(orderId));
    scope.join();
    scope.throwIfFailed();
    return new OrderSummary(inventory.get(), payment.get());
}

This round refines the Joiner contract (failure-propagating joiners now surface ExecutionException) and improves observability via JFR thread-dump events.

PEM encodings of cryptographic objects, 3rd preview (JEP 538)

No more hand-rolled PEM parsing or pulling in BouncyCastle for basic cert handling. New java.security API — PEMEncoder, PEMDecoder, and the sealed BinaryEncodable interface — encodes keys, certificates, and CRLs to PEM text and back. The third preview adds encrypted PEM support via password callbacks.

The incubator: Vector API, 12th round (JEP 537)

Still incubating, still waiting on Valhalla’s value classes before it can graduate. Expect movement in JDK 28.

Should you upgrade?

JDK 27 is non-LTS with six months of Oracle support — a poor fit for fleets pinned to long-term support, same as every between-LTS release. Its practical value is reconnaissance: these defaults will be in the next LTS, so running 27 now tells you what your future upgrade does to heap, GC pauses, and TLS handshakes. And with Valhalla’s value classes (JEP 401) merging toward JDK 28, the interesting half of the decade is just starting.

Sources

Keep reading