JDK 27 New Features Explained: What Actually Changes

JDK 27 ships nine JEPs, but only a few actually change your code today. What's default now, what's still preview, and what to do about each one now.

Status as of July 30, 2026: JDK 27 is in Rampdown Phase Two with its feature set frozen. The first release candidate is due August 6, the second August 20, and general availability is scheduled for September 15, 2026.

Nine JEPs shipped in the JDK 27 reference implementation. Most coverage of a Java release lists all nine with equal weight and moves on. That's the wrong way to read a release like this one: two of these features change your production heap and object layout the moment you upgrade, with zero code changes on your part. Four are previews you shouldn't put anywhere near production. The rest sit somewhere in between. Knowing which is which matters more than knowing that nine things happened.

JDK 27 is also a short-term release, not LTS, six months of support from Oracle and then you're expected to move on. If you're running a production LTS train, the honest framing is: this release is where you watch these features mature, not where you adopt most of them.

The nine JEPs, sorted by what they actually mean for you

JEP Feature Status Changes your code without you doing anything?
523 G1 as the default GC everywhere Final Yes, if you were relying on a different default
534 Compact object headers Now default Yes
527 Post-quantum hybrid TLS 1.3 key exchange Final Yes, if you use TLS 1.3
536 JFR in-process data redaction Final, opt-in No, you have to enable it
531 Lazy constants 3rd preview No, preview API
532 Primitive types in patterns/instanceof/switch 5th preview No, preview API
533 Structured concurrency 7th preview No, preview API
537 Vector API 12th incubation No, incubator API
538 PEM encodings of cryptographic objects Preview No, preview API

The top three are the ones worth reading closely even if you skim everything else. The rest are worth understanding, not worth building on yet.


Do you like what you are reading currently? Check out Implementing RSA in Python from Scratch for continued reading with us.
Implementing RSA in Python from Scratch
Build RSA encryption in Python from first principles. Covers the Extended Euclidean Algorithm, modular exponentiation, key generation, and working code you can run.

G1 becomes the default garbage collector everywhere (JEP 523)

Until now, the JVM's default GC selection logic favored G1 in server-class environments but could fall back to Serial GC on smaller machines, the kind of resource-constrained container or small VM a lot of self-hosted or budget-tier deployments actually run on. JEP 523 removes that branch: G1 is now what HotSpot picks regardless of environment, with an explicit goal that throughput, latency, memory footprint, and startup time shouldn't meaningfully regress for workloads that used to land on Serial by default.

If you've been explicitly setting -XX:+UseSerialGC on a small container because that's what you always do, nothing changes, your flag still wins. If you've never set a GC flag and have been running on whatever HotSpot picked automatically, this is the release where that automatic choice changes under you. Test it before you assume it's a non-event, Serial GC's low overhead was a real advantage on genuinely tiny heaps, and G1's regional design carries more bookkeeping cost that matters more as heap size shrinks, not less.

Compact object headers are now the default (JEP 534)

This one has a longer history than the headline suggests. Compact object headers shipped experimentally in JDK 24, moved to production status in JDK 25 but stayed opt-in behind -XX:+UseCompactObjectHeaders, and JDK 27 flips that switch: every object header on a 64-bit JVM shrinks from 96 bits to 64 bits by default, no flag required.

That's not a rounding error at scale. Every object on the heap carries a header, and shaving a third off that fixed cost compounds across millions of objects in a typical service. The practical effect is smaller heaps for the same live data, better deployment density, and better data locality since more of your actual objects fit in cache lines that used to be partly consumed by header overhead. If you're upgrading from JDK 26 or earlier and never explicitly enabled this flag, expect your heap usage numbers to shift the moment you switch, in a good direction, but shift nonetheless. Update your dashboards' baseline expectations before someone opens an incident over a heap graph that dropped for a good reason.

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

This is the feature that matters if you have any compliance timeline touching post-quantum cryptography, and it's easy to undersell because "post-quantum" sounds like a someday problem. JEP 527 adds hybrid key exchange algorithms to TLS 1.3, combining a quantum-resistant algorithm with a traditional one, so a connection is protected against both today's classical attacks and the harvest-now-decrypt-later threat model quantum computing eventually enables.

Hybrid is the operative word. This isn't "replace your key exchange with a post-quantum-only algorithm," which nobody's confident enough to ship as a sole default yet. It's "add the quantum-resistant option alongside the classical one," so a connection stays secure under either threat model without betting the whole handshake on an algorithm class that's still maturing. If your organization has a NIST post-quantum migration deadline on the calendar, JDK 27 is the release where the JVM side of that migration gets meaningfully easier.

[CONTENT CTA - place around the 70% mark: link to the CVE-2026-31431 post, framed around "if a TLS and cryptography change like this caught your attention, here's our deep dive on a real-world vulnerability that's worth understanding in the same context."]

Structured concurrency, seventh preview, and it's still not done (JEP 533)

Structured concurrency has been previewing since JDK 19. That's not a knock, it's context: this is the seventh consecutive preview round, and JDK 27's changes are meaningful enough to actually matter if you've been experimenting with earlier versions.

The core idea hasn't moved: StructuredTaskScope lets you treat a group of related subtasks running on separate threads as one unit, forked together, joined together, so nothing outlives the scope that created it. Combined with virtual threads, which make spinning up huge numbers of threads cheap, structured concurrency is the piece that keeps cheap threads from turning into leaked threads.

What changed in JDK 27: StructuredTaskScope and its Joiner interface gained a third type parameter, R_X, representing the exception type that join() can throw. The standard joiners now throw ExecutionException instead of the FailedException used in earlier previews, matching the exception type developers already expect from Future.get(). If you've been experimenting with earlier previews and catching FailedException, that catch block needs to change:

// Enable preview to compile and run:
// javac --release 27 --enable-preview Main.java
// java --enable-preview Main
 
try (var scope = StructuredTaskScope.open()) {
    Subtask<String> userTask = scope.fork(() -> fetchUser(userId));
    Subtask<String> ordersTask = scope.fork(() -> fetchOrders(userId));
 
    scope.join();
 
    String user = userTask.get();
    String orders = ordersTask.get();
    return new Profile(user, orders);
} catch (ExecutionException e) {
    // JDK 27: was FailedException in earlier previews
    throw new ProfileLoadException(e.getCause());
}

The structural guarantees that made this API worth watching in the first place haven't changed: subtasks inherit ScopedValue bindings, a StructureViolationException fires if you use a scope outside try-with-resources or fork from a thread that doesn't own it, and thread dumps expose the scope hierarchy for debugging. Preview 7 is refinement, not a redesign.

The practical guidance hasn't changed either: preview APIs don't carry the same compatibility guarantees as finalized ones, and code built against --enable-preview in JDK 27 may need changes again if the API shifts before finalization. Experiment freely. Don't ship it as load-bearing production code yet.

Lazy constants, third preview, and a genuinely useful new factory method (JEP 531)

Every Java codebase has some version of the class-holder idiom, a nested static class that leans on the JVM's lazy class-initialization behavior to defer creating an expensive singleton until it's actually needed:

class OrderController {
    public static Logger getLogger() {
        class Holder {
            private static final Logger LOGGER = Logger.create(...);
        }
        return Holder.LOGGER;
    }
}

It works, but it's a workaround, not an API, and it doesn't compose well when you want the same deferred-init behavior for a field rather than a whole class. java.lang.LazyConstant<T> is the actual API for this: define it with a factory function, and the value computes on first access, caches, and is treated by the JVM as a true constant afterward, meaning it gets the same constant-folding optimizations that final fields get, with more flexibility about exactly when initialization happens.

JDK 27's revision removes two low-level methods, isInitialized() and orElse(), that the JEP authors concluded encouraged patterns working against the API's actual design goals, fine-grained inspection isn't really compatible with "true constant" semantics. In their place, JDK 27 adds Set.ofLazy(...), which completes the set: lazy versions now exist for all three fundamental collection types, List, Set, and Map.

private static final LazyConstant<Config> CONFIG =
    LazyConstant.of(() -> Config.loadFromDisk());
 
public static Config getConfig() {
    return CONFIG.get(); // computed once, cached, then treated as a true constant
}

This is a preview API, same caveat as structured concurrency applies, but it's a smaller and more contained one, and three consecutive preview rounds with converging rather than expanding scope is usually a sign an API is getting close to done.

Primitive types in patterns, instanceof, and switch: no changes this round, which is the interesting part (JEP 532)

This is the fifth preview of this feature, and JDK 27 makes zero changes to it. That's worth reading as a signal rather than a non-event: a feature that's been previewed five times without the design team finding anything left to adjust is usually a feature nearing finalization, not one still being actively reshaped.

The feature itself extends pattern matching, instanceof, and switch to work with primitive types the same way they already work with reference types, closing a gap that's existed since switch expressions first shipped:

long v = someValue();
switch (v) {
    case 1L -> handleOne();
    case 2L -> handleTwo();
    case 10_000_000_000L -> handleLarge();
    case long x -> handleOther(x); // primitive type pattern
}

Before this, switch on primitives beyond int and a handful of special cases meant falling back to a chain of if statements, exactly the kind of boilerplate pattern matching was supposed to eliminate. If you've avoided using primitive types in switch expressions because the ergonomics weren't there yet, this is the JEP to watch for finalization.

JFR in-process data redaction (JEP 536)

This is a quiet, unglamorous feature that fixes a genuinely embarrassing failure mode. JDK Flight Recorder captures command-line arguments, environment variables, and system properties as part of normal profiling data, which is exactly where a lot of teams put secrets: access tokens in an environment variable, a database password passed as a system property, an API key on the command line. Before this JEP, all of that could end up sitting in a .jfr recording file, unredacted, potentially shipped to whoever you handed the recording to for performance analysis.

JEP 536 lets you redact that data before it leaves the process. It's opt-in, you have to configure it, but if your team ever attaches JFR recordings to support tickets or shares them with a vendor for performance troubleshooting, this closes a real, previously-quiet leak path.


Are you into self hosting your own ChatGPT or Claude AI Agent? We have a guide to be able to help you do the same at Build Your Own AI Agent Stack: The Complete Self-Hosted Guide for 2026 to how to do it.
Build Your Own AI Agent Stack: The Complete Self-Hosted Guide for 2026
Build a production-grade self-hosted AI agent from scratch using Docker Compose. Covers the full stack — Ollama, n8n, Flowise, Qdrant, SearXNG, Langfuse, and Redis.

The Vector API, twelfth incubation (JEP 537)

Twelve rounds of incubation is a long road, and it reflects how genuinely hard it is to build an API that reliably compiles down to optimal SIMD instructions across different CPU architectures without either over-promising performance or under-using the hardware. The goal remains the same as it's been since incubation started: express vector computations in a way that compiles at runtime to the best available vector instructions on whatever CPU you're actually running on, beating equivalent scalar code without hand-writing architecture-specific intrinsics.

PEM encodings of cryptographic objects (JEP 538)

A smaller, practical convenience: an API for encoding and decoding cryptographic keys, certificates, and certificate revocation lists in the standard PEM transport format, without reaching for a third-party library just to move a key in and out of the format literally every TLS tool on earth already uses. Preview status, same caveats as the others, but low-risk to experiment with given how narrow and well-understood the PEM format itself is.

What to actually do with this release

If you're on a production LTS train, JDK 27 isn't a migration target, it's six months of preview features maturing in public before some of them land in JDK 29, the next LTS, expected in 2027. Read this release to know what's coming, not to plan a Q4 upgrade around it.

If you're not on LTS and you do track current releases, the two features that change your numbers without you writing a line of code, G1 everywhere and compact object headers by default, deserve an actual benchmark pass against your current heap and GC metrics before you assume the upgrade is a non-event. Everything still in preview or incubation is worth experimenting with in a branch, not worth building production code around, preview APIs have changed shape release over release for years running, and JDK 27 is no exception.

CoderOasis covers the Programming and SysAdmin side of self-hosting, if you're running any JVM-based service as part of your own stack, whether that's a self-hosted app with a Java backend or a Minecraft server built on one of the JVM-based server platforms, GC and heap behavior changes like the two above are worth tracking release over release, not just at major LTS boundaries.