Feature Freeze for JDK 27: What Will the New Edition Bring? - JVM Weekly vol. 179
Today JDK 27 forks off into the Rampdown phase - so let's look at what made the cut!
Today is the day: June 4th, JDK 27 enters the Rampdown phase and forks off the main line. Rampdown means the feature list gets frozen and no further new features are to be expected - so the set you see below is the set we’re getting in September.
Just like JDK 26, this is a lean release - and, as tradition now dictates, a non-LTS one (the second non-LTS since the JDK 25 LTS last September; the next LTS won’t arrive until JDK 29 in 2027).
Yeah, I know there is not a thing like LTS in Java.
So if you were waiting for a blockbuster on the scale of HTTP/3, this isn’t it. But don’t let the size fool you - there’s one genuinely important security headline here, and two quietly consequential default flips that will touch almost every Java process on the planet.
For the calendar-minded, the full schedule, approved by Mark Reinhold:
Rampdown Phase One (fork from main line): June 4, 2026
Rampdown Phase Two: July 16, 2026
Initial Release Candidate: August 6, 2026
Final Release Candidate: August 20, 2026
General Availability: September 14, 2026
Now, let’s go through the complete list of changes in the new edition.
Cryptography Updates
JEP 527: Post-Quantum Hybrid Key Exchange for TLS 1.3
If there’s a single reason to pay attention to JDK 27, this is it. JEP 527 brings post-quantum cryptography into the TLS handshake itself - and, crucially, it does so by default, with zero code changes on your side.
The problem it solves: quantum computers capable of breaking RSA and Elliptic-Curve Diffie-Hellman don’t exist yet. But that’s cold comfort, because of the wonderfully ominous “harvest now, decrypt later” threat model: an adversary records your encrypted traffic today, sits on it, and decrypts it the day a sufficiently large quantum computer shows up. For anything that needs to stay secret for years (medical records, state secrets, that embarrassing Slack export), the clock is already ticking.
The solution: the IETF TLS Working Group settled on hybrid key exchange - combine a battle-tested classical algorithm with a quantum-resistant one, and you’re safe as long as either survives. This hedges against quantum attacks while acknowledging that the new lattice-based algorithms haven’t yet had decades of cryptanalysis thrown at them. JDK 27 implements three such schemes, each pairing ML-KEM with Ephemeral Elliptic-Curve Diffie-Hellman (ECDHE):
X25519MLKEM768 - X25519 + ML-KEM-768
SecP256r1MLKEM768 - secp256r1 + ML-KEM-768
SecP384r1MLKEM1024 - secp384r1 + ML-KEM-1024
This is the natural next step in a multi-release journey: the KEM API arrived in Java 21 (JEP 452), the ML-KEM algorithm itself in Java 24 (JEP 496), and now JDK 27 wires them into TLS 1.3.
The best part: TLS clients advertise their supported “named groups” in preference order during the handshake, and X25519MLKEM768 goes straight to the front of that list. So any code using javax.net.ssl gets quantum-resistant handshakes automatically - provided it doesn’t already pin specific named groups. If you do want to take control, you can override the default list via the jdk.tls.namedGroups system property, or per-connection:
SSLSocket tlsSock = (SSLSocket) SSLContext.getDefault()
.getSocketFactory().createSocket();
SSLParameters params = tlsSock.getSSLParameters();
// Two hybrid KEM schemes plus two traditional ones
params.setNamedGroups(new String[] {
"SecP256r1MLKEM768", "X25519MLKEM768", "secp256r1", "x25519"
});
tlsSock.setSSLParameters(params);One caveat worth flagging (and the JEP authors flag it too): the underlying IETF specifications for hybrid key exchange are still in draft status. The feature ships only once those drafts graduate to RFCs - so if the standard wobbles, this could in theory slip. But everything points to it landing.
JEP 538: PEM Encodings of Cryptographic Objects
Sticking with the security library, JEP 538 finally finalizes the PEM encoding/decoding API after a patient two-preview run - JEP 470 in JDK 25 and JEP 524 in JDK 26 (longtime readers will recall me covering both).
For the uninitiated: cryptographic objects live in binary DER format - great for machines, unreadable for humans. PEM (Privacy-Enhanced Mail) wraps that DER in Base64 with those familiar -----BEGIN ...----- headers, which is what actually ends up in your Git repos, server configs, and copy-pasted email threads. Until this API, Java had no first-class way to convert between the two, so everyone reached for hand-rolled parsers or third-party libraries.
String pem = PEMEncoder.of().encodeToString(privateKey); // object → PEM
PrivateKey key = PEMDecoder.of().decode(pem, PrivateKey.class); // PEM → objectThe finalization brought two notable tweaks. The PEMRecord class was reclassified from a record to a regular class, which lets it offer convenience constructors that accept Base64-encoded content directly as byte arrays. And the marker interface formerly known as DEREncodable was renamed to BinaryEncodable - a more honest description of the binary data actually sitting inside the PEM text. Nothing earth-shattering, but it’s nice to finally retire the “(Preview)” suffix.
Squeezing the Memory
JEP 534: Compact Object Headers by Default
JEP 534 promotes compact object headers to the default layout in HotSpot. The feature itself is the one we met in JDK 25 as a production option (JEP 519), having started life as an experimental flag back in JDK 24.
Quick refresher on why anyone cares: the standard 64-bit HotSpot object header is two machine words - a mark word and a class pointer. With millions of live objects, that second word adds up to tens of megabytes of pure overhead, doubles GC traffic, and trashes your CPU cache. Compact headers squeeze the mark word, a compressed class pointer, and a few meta bits into a single 64-bit value, while keeping all the functionality you rely on (hashCode, synchronization, locking).
After large-scale production hardening (Amazon famously ran it across hundreds of services), the layout has proven itself enough to become the default. From JDK 27 onward you get the memory savings for free - and if for some reason you need the old behaviour, -XX:-UseCompactObjectHeaders is still there to turn it off. A rare case of a release where you do less and get more.
JEP 523: Make G1 the Default Garbage Collector in All Environments
JEP 523 is the kind of change that sounds boring until you realize how many edge cases it quietly eliminates. As nipafx would gleefully remind us, the JCP doesn’t technically recognize the concept of a “default garbage collector” - but in practice HotSpot has to pick something when you don’t, and that pick has been inconsistent for a decade.
Here’s the history: G1 became the default for server-class machines in JDK 9 (JEP 248) - that is, anything with at least 2 hardware threads and roughly 2 GB of RAM. Below that threshold (single CPU, less than ~1792 MB), HotSpot silently fell back to Serial GC, because back in 2017 Serial genuinely had the edge on throughput and footprint in cramped environments.
Note the past tense. Over the last several releases G1 has been relentlessly optimized: its native memory footprint came down, and the second card table introduced by JEP 522 in JDK 26 (the one I spent way too many paragraphs explaining in vol. 157) brought its throughput within spitting distance of Serial’s. G1’s maximum latencies, meanwhile, have always beaten Serial’s, since it reclaims the old generation incrementally rather than via stop-the-world full collections.
So the conclusion writes itself: G1 is now competitive at all heap sizes, and there’s no longer a good reason to surprise users with a different collector just because they’re running on a small box. From JDK 27, if you don’t specify a GC, you get G1 - period, regardless of cores or memory. A welcome side effect: the AlwaysActAsServerClassMachine and NeverActAsServerClassMachine flags lose most of their purpose and are now deprecated for removal. And of course, an explicit -XX:+UseSerialGC still does exactly what it says - this only changes the implicit choice.
Observability & Tooling
JEP 528: Post-Mortem Crash Analysis with jcmd
JEP 528 wants to extend the trusty jcmd tool so it can diagnose a JVM after it has crashed - poking at core dumps to extract the kind of post-mortem detail you’d otherwise need the aging jhsdb utility or the Serviceability Agent for. The intent is consolidation: stop spreading “what happened when it died” across three different tools and bring it home to jcmd, where the living-JVM diagnostics already live.
Why does moving this into jcmd matter? Because jhsdb and the Serviceability Agent are the crusty, half-forgotten corner of the toolbox - powerful, but fiddly, poorly documented, and the kind of thing you re-learn from scratch every time production falls over at 3 a.m. jcmd, by contrast, is the modern, unified entry point most of us already reach for. Folding crash analysis into it means one tool, one mental model, for both the JVM that’s still breathing and the one you’re performing an autopsy on. This one had me a little nervous right up to the freeze - but it made the cut, and I’m genuinely glad it did.
JEP 536: JFR In-Process Data Redaction
JEP 536 plugs a leak that’s bitten more teams than will admit it. JDK Flight Recorder captures, among other things, your command-line arguments, the initial values of environment variables, and system properties - which is fantastic for debugging and terrible when that .jfr file (often containing a database password or API token passed via -D...) gets shipped off to a vendor, a support ticket, or a shared bucket.
This JEP lets JFR redact sensitive information in-process, before the recording is even finalized, so the secrets never make it to disk in the first place. It’s the sort of unglamorous, defense-in-depth feature that nobody asks for in a survey but everybody benefits from. Currently Proposed to Target, but it looks comfortable.
Nihil Novi Sub Sole - (Yet More) Preview Features
And now the part of the release notes where, as ever, the preview features file past for another lap. If you’ve been reading this newsletter for any length of time, you can probably recite these from memory - but let’s note what (little) changed.
JEP 531: Lazy Constants (Third Preview)
The feature with more names than a witness in protection: Computed Constants → StableValue (JEP 502, JDK 25) → LazyConstant (JEP 526, JDK 26) → and now JEP 531 for round three.
The core idea has been stable throughout: compute a value exactly once, lazily, in a thread-safe way, and let the JVM treat it as a true constant afterwards (constant-folding and all), so you get lazy initialization without paying the usual synchronization tax.
The third preview makes two changes: it removes the low-level isInitialized() and orElse() methods (they invited usage patterns at odds with the API’s design goals), and it adds a Set.ofLazy(...) factory. With that addition, all three fundamental collection types - List, Set, and Map - now have lazy variants where each element sits in its own LazyConstant and initializes only on first access.
As I showed back in vol. 171, this lets you write things like:
enum Option { VERBOSE, DRY_RUN, STRICT }
// Each element initialized lazily, only on first touch
static final Set<Option> OPTIONS =
Set.ofLazy(EnumSet.allOf(Option.class), Application::isEnabled);Thread-safe by construction, constant-foldable after the fact. The feature matures a little with each iteration and is clearly circling the runway toward finalization - my bet is JDK 28.
JEP 532: Primitive Types in Patterns, instanceof, and switch (Fifth Preview)
JEP 532 brings back - for the fifth time (yes, fifth) - the extension of pattern matching to primitive types across instanceof and switch. This round ships with no changes from the fourth preview (JEP 530, JDK 26), so... you know the drill by now 😉
switch (x.getYearlyFlights()) {
case 0 -> "No flights";
case int i when i >= 100 -> "Gold status!";
case int i -> "Regular: " + i + " flights";
}The authors are simply collecting more real-world feedback before pulling the trigger. One day this will be stable, and we’ll all pretend we weren’t slightly impatient about it.
JEP 533: Structured Concurrency (Seventh Preview)
JEP 533 continues the marathon - the seventh preview of Structured Concurrency, following JEP 525 (sixth preview) in JDK 26. The API treats a group of concurrent subtasks as a single unit of work, with unified cancellation and error propagation, and you still drive it through the StructuredTaskScope.open() factory established in the big sixth-preview revamp:
try (var scope = StructuredTaskScope.open()) {
Subtask<String> user = scope.fork(() -> findUser());
Subtask<Integer> order = scope.fork(() -> fetchOrder());
scope.join();
return new Response(user.get(), order.get());
}At seven previews, Structured Concurrency is officially testing my ability to find new ways to say “still cooking.” But it’s good cooking, and the shape now feels right.
JEP 537: Vector API (Twelfth Incubator)
And to close, our perennial guest: JEP 537 delivers the twelfth incubation of the Vector API, with no substantial implementation changes since JDK 25. The pitch is unchanged - express vector computations in plain Java that the JIT reliably compiles down to optimal SIMD instructions (AVX, NEON, SVE) on the host CPU.
As I’ve now written so many times it should be a macro: the Vector API stays in incubation until Project Valhalla‘s value classes become available as preview features, at which point it’ll be adapted to use them and promoted to preview. Back in the vol. 157 (JDK 26) edition I admitted I’d honestly expected more Valhalla by now. Reader, I am still waiting. The Vector API and I are growing old together.
So there it is - JDK 27, the second lean non-LTS release in a row. No single feature reorders the universe the way HTTP/3 did in 26, but the shape of the release tells a story: security is hardening (post-quantum TLS by default is a genuinely big deal that most people will never have to think about, which is exactly the point), the defaults are getting saner (G1 everywhere, compact headers on), and the long-running language and concurrency previews keep inching toward the finish line while we collectively hold our breath for Valhalla.
And with that, the list is frozen - no loose threads to report back on this time. See you in the next edition - Friends of OpenJDK (Foojay.io) One 😊. We needed to switch them this time









