Java 27 is here and brings four highly targeted updates and five previews/incubators. We sit down and look closer at changes into the garbage collector, JFR improvements, post-quantum TLS 1.3, and compact object headers, which reduces memory usage.

In the article about Java 25, we've already mentioned compact object headers that we could turn on with a flag. Now there's no need to use the flag.
At that time, we only touched on this topic very briefly. Now that it's on by default, so we're going to break it down once again but deeper.
Every object in the heap begins with a service header. In the classic layout (in most JVMs), that's two blocks:
In total, we get 96 bits (12 bytes) per object, even if it contains very little useful data. Compact object headers compress all of this into 64 bits: the pointer to the class metadata is compressed down to 22 bits.

In theory, saving a few bits is a good thing, though, but what benefits shall we (as JRE users) actually get out of it?
Not long ago, we've released a JavaScript/TypeScript analyzer powered by Java 25. We use it as a test subject, "set it loose" on the Grafana source code, and then run it with and without compact object headers.
Three minutes passed and we got this:
|
Benchmark |
With flag |
Without flag |
|---|---|---|
|
Time |
81.4 sec |
79.4 sec |
|
Committed heap |
1 290 240K (~1.23 GB) |
1 126 400K (~1.07 GB) |
|
Used heap: output |
1 150 092K (~1.1 GB) |
773 062K (~738 MB) |
Committed heap represents the amount of memory the virtual machine has allocated from the OS and is keeping reserved. Used heap is a memory in use right now—in another words, the number of bytes occupied by live objects that have not yet been removed by the garbage collector.
As a result, analysis time remained almost unchanged (within margin of error), committed heap decreased by 13%, and used heap shrank by 33% in total! Just one flag reduced our app's memory usage by a full third, wow!
What's the cost of our joy? A 22-bit pointer limits the number of loaded classes to approximately four million. There is no automatic rollback to a non-compact mode—if the limit is exceeded, we'll get an OutOfMemoryError. If your app really hits this limit, you may see a true pleasure in long-lived streams. But if you want to revert to the previous mode, you can apply manually another flag:
java -XX:-UseCompactObjectHeaders Application
Prior to Java 27, the JVM would check the machine at its start, and if it detected fewer than two processors or less than 1,792 MB of memory, it'd just use Serial GC instead of G1. That kind of machine got labeled "client" machine, and the memory allocator best suited for it was used.
Now, guess what a modest JVM container with a limit of one CPU and 512 MB of memory looks like?
Right. A part of the containerized Java has been running on a client-oriented collector all this time, even though no one consciously made that decision. Worse yet, the same app could obtain different GCs on the server and in the developer's IDE, which turned reproducing the pausing issues into the thrilling quest with a touch of manic laughter.
Java 27 stopped taking the environment into account and now G1 is selected by default:
# Java 25
$ java -XX:ActiveProcessorCount=1 -Xmx256m -Xlog:gc --version
[0.001s][info][gc] Using Serial
# Java 27
$ java -XX:ActiveProcessorCount=1 -Xmx256m -Xlog:gc --version
[0.001s][info][gc] Using G1
The reasoning is simple: improvements to G1's throughput in previous releases have reduced the gap with Serial to almost zero, and G1's pauses are noticeably more pleasant because it can operate concurrently and in parallel. Plus, predictability: the application's behavior no longer depends on how many cores the JVM detected at start.
If you specifically want to use Serial—it still has the lowest memory footprint and faster startup time—you can still leverage it: -XX:+UseSerialGC.
Ever heard of "harvest now, decrypt later"?
This is an interesting category of threats, though: attackers, having access to your traffic but unable to break the TLS connection, simply save the encrypted traffic for the future.
Why would they do that, what's the purpose? But the purpose is very curious: attackers have a strong faith in global progress and are waiting for the miracle of a quantum computer capable of breaking elliptic curve key exchange. Medical and banking records, encryption keys, personal data—all of these will still be worthy ten years from now, so the scheme is quite strong.
Following HTTP/3 (see the previous article), support for TLS 1.3 is now available. The session key in TLS 1.3 now gets generated using two algorithms simultaneously: a classic elliptic curve key exchange + a quantum-resistant algorithm. Such a pairing is called a hybrid group, and the point is that the connection remains secure as long as at least one of the two algorithms is still in service. So this update makes it pointless to wait for a coming of the quantum computer.
For the application, this is not expensive in terms of code: javax.net.ssl automatically sets the hybrid group as the first in the preference list. If the other side supports it, the exchange gets a hybrid approach; if not, everything works as before. There's nothing to touch, unless you've manually set a list of groups via jdk.tls.namedGroups or SSLParameters::setNamedGroups.
But in some cases, we'll still have to pay a little extra: a TLS handshake costs about two KB more. For most applications, this goes unnoticed, but with millions of short connections in the traffic, it becomes noisier.
An important note: this JEP applies only to key exchange. Certificate signatures are still in the classic format, and this is a deliberate choice: it is impossible to forge a signature retroactively, but it is entirely possible to decrypt recorded traffic.
When recording, JFR carefully preserves the entire process environment: environment variables, system properties, and command-line arguments. And that's where, as we all know, database passwords, tokens, and keys to everything tend to live. That's why sending a .jfr file to a colleague—and especially attaching it to a public issue—always came with a slight twinge of anxiety. In most cases, you shouldn't make .jfr files publicly available at all without cleaning them up first.
Starting with Java 27, JFR scrubs sensitive values within the JVM before they're passed to disk. And what does JFR consider to be sensitive data? By default, any environment variable or system property whose name matches one of these patterns gets replaced with [REDACTED]:
*api*key* *auth* *client*secret* *credential*
*jaas*config* *passphrase* *passwd* *password*
*private*key* *pwd* *secret* *token*
Command-line arguments of the form --db-password qwerty123 are handled in the same way.
We can expand the list of properties using the -XX:FlightRecorderOptions:redact-key=+<your-pattern> flag.
It's worth to understand the limits here: the mechanism covers start events (environment variables, system properties, arguments, and JVM information) and is explicitly described as best-effort. It doesn't filter your app's events, exception messages, or child process command lines. In other words, .jfr has become significantly safer now, but not sterile in total. We're still worried, but not as much as before.
A few more JEPs have been included in the release as previews. We'll peruse and overview them after the release of their final versions. Here are some links for you to explore on your own:
The full list of JEPs is here.
This was a minor release, and there wasn't anything here that could be a 180-degree-turn change in development: they've updated the preview and polished up the platform. And that brings useful thing right out of the box for JVM: objects on the heap are more compact, the garbage collector is the same for both the server and a tiny pod, JFR logs no longer be brimming with secrets, and TLS is preparing for a quantum future.
Essentially, this is preparation for the next LTS. Structured Concurrency is now on its 7th preview, primitives in patterns are in their fifth preview, and the Vector API is waiting for Valhalla. All of this is being finalized so that it can be delivered ready to go with Java 29. Well, for now, though, let's just update and get a more compact heap for free.
0