Java 27 is only boring on the surface.

Half a year ago, I wrote an article with Lutske de Meer on why Java 26 is boring and why it is a good thing. Being boring also means being stable and predictable. Java 27, which came out the day this blog post comes out, is no different. While the language and the runtime library surface didn’t change with this release and only four non-preview JDK Enhancement Proposals (JEPs) got merged, the JVM changed significantly under the hood. So in this blog, I’ll tell you all about the new JEPs, how the preview ones have changed since JDK 26, and the notable changes in the JDK you might not read elsewhere (except in the release notes, which this text is heavily based on and Artur Skowroński‘s well researched JVM Weekly).

TL;DR: Java/JDK 27 got some new defaults and refinements, but the most consequential change is the removal of the JVM compiler interface.

Big Changes and JEPS

  • G1 GC is now the default GC (JEP 523)
  • Post-Quantum Hybrid Key Exchange for TLS 1.3 is now in (JEP 527)
  • Lazy Constants has its third preview (JEP 531), refining the API
  • Primitive Types in Patterns, instanceof, and switch has its fifth preview (JEP 532) with no real changes
  • Structured Concurrency (JEP 533) has its seventh preview, with minor API adjustments
  • Compact Object Headers are now the default (JEP 534)
  • JFR In-Process Data Redaction is now implemented (JEP 536)
  • The Vector API gets its twelfth incubator (JEP 537), with only minor changes, probably waiting for Valhalla/value types
  • PEM Encodings of Cryptographic Objects (JEP 538) gets its third preview, polishing the API
  • TLS 1.3 has compression support. Now JDK 27 supports zlib-based compression, but not zstd or brotli (JDK-8372526)
  • The jcmd tool has bash autocompletion (JDK-8357439)
  • -XX:FlightRecorderOptions=help is implemented
  • JDK 24 introduced security properties to specify, for example, TLS properties, JDK 27 gives you jcmd ... VM.security_properties to easily see them
  • Some more crypto stuff (JDK-8347938, JDK-8374808, JDK-8372351, JDK-8369917, JDK-8384353, JDK-8378893, JDK-8373426, JDK-8044609, JDK-8377550)

RIP to these removed features

  • ThreadPoolExecutor.finalize() was removed after being deprecated in JDK 9, and it was a no-op since JDK 11 (JDK-8371748)
  • The JVM compiler interface (JVMCI) got removed (JDK-8382582)

Notable Fixes

Notable other changes

  • The JFR event jdk.SystemProcess doesn’t show CLI arguments anymore (JDK-8384164)
  • The built-in HttpServer changes the path matching from simple string prefix matching to path prefix matching, e.g. /foo doesn’t match /foobar anymore (JDK-8272758)
  • The DateTimeFormatter supports short time zone codes like “+01” alongside long ones like “+01:00” now (JDK-8210336)
  • GZIPInputStream (JDK-8385924) and ZipOutputStream (JDK-8380452) got minor fixes
  • “The JSON Format Thread Dump Now Generates Thread Identifiers, Thread Counts, and the Process Identifier as Numbers (JDK-8381002)”
  • “The Java Compiler Now Correctly Rejects Characters That Appear in Source Code After an ASCII SUB Character (JDK-8371873)”
  • “Java Launcher No Longer Allows Inheriting a Package-Private main Method from Another Package (JDK-8377004)”
  • “TYPE_USE Annotations on a var Lambda Parameter Are Rejected (JDK-8371683)”

In the following, I’ll first cover the JEPs (in the order shown above), ignoring the self-explanatory ones, and afterward I’ll cover a subset of the notable other changes that I find especially interesting or noteworthy (and JVMCI).

G1 GC is now the default GC (JEP 523)

Over the last JDK releases, the G1 garbage collector has gotten faster and faster. For example, in JDK 26, Ivan Walulya and Thomas Schatzl significantly improved throughput. For many years, G1 has been the default garbage collector for all but the most constrained systems. In systems with a single CPU or less than 1792 MB of physical memory, the JVM selected the Serial garbage collector by default, as this GC had better performance characteristics in these scenarios.

To quote the Sip of Java:

The Serial GC performs all GC work on a single thread, reducing overhead as there is no need to coordinate efforts between threads. This makes the Serial GC ideal for running on systems with a single processor and/or limited resources. The Serial GC, however, can still be a valid choice for applications on multi-processor machines as long as the dataset remains under 100MB.

But with all the recent improvements, G1 is now close enough in all performance metrics (and better in many), even on small systems, that there is no proper justification for just making G1 the default garbage collector in every scenario.

This means G1 is now the primary garbage collector for OpenJDK. You can learn more about the optimization efforts in an old, yet still interesting talk by Thomas Schatzl at FOSDEM:

Also, you can find an even older intro into G1 here:

This all shows how there is always opportunity to improve existing GCs, even with never GCs like ZGC carving a niche for themselves.

Lazy Constants (JEP 531)

The lazy constants are steadily evolving, and a way to have on-demand initialization for fields. To quote what the JEP did from the JEP:

  • We re-oriented the API to focus on high-level use cases, removing the low-level methods orElseSet, setOrThrow, and trySet. This left only factory methods that take value-computing functions.
  • We renamed the API from StableValue to LazyConstant. The former was appropriate for a low-level API that was close to the underlying JVM mechanism upon which it is implemented; the latter better captures the primary intended high-level use case, namely that of a single constant value that is initialized lazily.
  • We enhanced discoverability by moving the factory methods for lazy lists and maps into the java.util.List and java.util.Map interfaces, respectively.
  • We further simplified the API by removing the function and intFunction factory methods, which provided only marginal benefits over lazy lists and maps.
  • We disallowed null as a computed value in order to improve performance and better align lazy constants with constructs such as unmodifiable collections and ScopedValue.

…

Future Work

Lazy constants cover the common, high-level use cases for lazy initialization. In the future we might consider providing stable access semantics directly, at a lower level, for reference, array, and primitive fields. This would address, for example, use cases where the computing function associated with a lazy constant is not known at construction.

Therefore, we can expect further refinements in the future and hope the feature remains stable until the next LTS, although there currently seem to be no API changes planned for JDK 28.

Compact Object Headers are now the default (JEP 534)

One of the big changes in JDK 25 was the introduction of compact object headers, at the time guarded behind a flag:

Every object needs a header on the heap with information,,such as the object’s class, the computed hash code,, and some GC-specific state. Roman Kennke worked hard to compress these headers, reducing memory consumption in some benchmarks by up to 22% (especially when there are many small objects). This also improves CPU cache utilization and can lead to runtime improvements.

Since JDK 25, well, actually since JDK 24 when the feature was added as experimental, the compact object headers have matured to a point where not enabling it is pointless.

SapMachine has had this feature enabled since JDK 25 because the benefits were so significant.

JFR event jdk.SystemProcess drops CLI arguments

A small, maybe overlooked change is that the JFR jdk.SystemProcess event has been refined. This event emits which other processes are running alongside the JVM on the same machine, one process per event. The tiny change in a81d029 changed the behavior from emitting the whole command line to emitting only the process path/binary. The now-omitted CLI are of minimal use for profiling and monitoring the JVM, but can leak potentially sensitive information.

Interestingly enough, this is not a change on macOS or Windows, as the actual behavior there has been, from the beginning, to just store the program path. So this change only affects Linux.

Is this an important change, then? Kind of, as it aligns the different platforms and shows the continued improvement of JFR.

JVMCI is removed

The biggest change in this release is that the Java-Level JVM Compiler Interface has finally been removed in JDK 27. The goal of this JDK component was to “allow a Java component programmed against the JVMCI to be loaded at runtime and used by trusted Java code to install machine code in the JVM that can be called via a Java reference to the installed code.” (JEP 243). The JVMCI was mostly used as a way to use the GraalVM JIT inside HotSpot, but isn’t anymore, so rest in peace:

JVMCI is still used this way because JDK 25 still has support. But this will fade out. JVMCI was also the foundation of TornadoVM, which allows you to run Java on GPUs, but they replaced it.

If you want to learn more about this technology, please view the FOSDEM talk by my colleague Martin Doerr:

But why did the JVMCI go? Mainly, Oracle fully separated the GraalVM and OpenJDK, which led to GraalVM not using JVMCI in the future. Therefore, the cost of maintenance of JVMCI was deemed too high:

In practice, this gives HotSpot two compiler-facing views of VM internals that must be kept consistent.

That consistency has become an expensive ongoing maintenance cost. JVMCI is not isolated in one directory; it affects shared compiler and runtime code, metadata handling, deoptimization, code cache management, GC support, serviceability, flags, tests, and build logic. The removal PR demonstrates this directly: removing JVMCI/Graal references requires changes across many parts of the JDK, and eliminates hundreds of JVMCI-related conditionals, including 252 ‘#if INCLUDE_JVMCI’ blocks. This is not dead weight that sits quietly; it is complexity that many unrelated changes have to carry.

….

Project Valhalla provides another concrete example. In deoptimization code, there is now a new path where C2 would provide refined array-property information, while JVMCI currently falls back to default properties. This illustrates the recurring pattern: new VM work cannot simply move forward in one well-understood implementation path; it must either extend JVMCI as well, add conservative fallback behavior, or carry incomplete special handling. That slows down projects like Valhalla and increases the risk of subtle differences between execution modes.

JVMCI also expands the build and test matrix. …

Removing it from the JDK reduces complexity, lowers risk, and lets future HotSpot work proceed with fewer alternate paths to preserve.

JDK-8382582: Remove the experimental JVMCI feature (in THE Java Bug Tracker)

There were also people responding to this proposal with a belief that the JVMCI is still important enough to keep, most notably Paul Hohensee from Amazon/Corretto:

I’d like to request reconsideration of this action. There are many projects which depend on JVMCI, including Amazon (GraalJS at scale), JRuby, TornadoVM, and (at last count) 433 Maven Central GraalJS dependencies, see https://mvnrepository.com/artifact/org.graalvm.js/js. See the hotspot-compiler-dev list threads at

https://mail.openjdk.org/archives/list/hotspot-compiler-dev@openjdk.org/thread/E626GML4PPAOR326DHB45XE3LMWRTVOP/
https://mail.openjdk.org/archives/list/hotspot-compiler-dev@openjdk.org/thread/G4AFXNYXO2JQRHYDQYKHXGIO3DEFWNBG/

Comment on JDK-8382582 by Paul HohenSee

But the conclusion was then:

While retaining this experimental functionality may appear beneficial,
the long-term maintainability of the platform has to remain the
priority. Unsupported or insufficiently maintained components carry real
costs in complexity, testing, and ongoing maintenance.

At this point, no compelling arguments have been raised that would
justify keeping the JVMCI support in its existing form. We will
therefore proceed to remove it.

Vladmimir Kozlov on the OpenJDK MailingList

Around the same time, the HotSpot Group at the OpenJDK decided to withdraw sponsorship of the Graal and Metropolis (JVMCI) project. The good news for Java users is that the decision to remove this feature was deliberate, after considering all arguments for and against.

Conclusion

Java seems to be moving slowly on the surface. Still, as I listed in this blog post, there are many other changes underway, including the many value object changes slated to come in as a preview in JDK 28, and the ongoing work to enable ahead-of-time compilation. But these features take time to develop and to mature. The current preview features give us a glimpse into the future of the language and runtime, with the runtime hopefully becoming ready for the next LTS release. Many of the other changes in this release aren’t as flashy as, say, virtual threads, but like the removal of JVMCI, will shape the OpenJDK for years to come.

Thanks for coming along with this blog post, and see you in one or two weeks on something (hopefully) related to native Java agents.

This blog post is part of my work in the SapMachine team at SAP, making profiling easier for everyone.

P.S.: Go up and down or down and up…

Frankfurt Airport moving walkway going down and up, at night, during my travel back from JavaZone

Author

  • Johannes Bechberger

    Johannes Bechberger is a JVM developer working on profilers and their underlying technology in the SapMachine team at SAP. This includes improvements to async-profiler and its ecosystem, a website to view the different JFR event types, and improvements to the FirefoxProfiler, making it usable in the Java world. His work today comprises many open-source contributions and his blog, where he regularly writes on in-depth profiling and debugging topics. He also works on hello-ebpf, the first eBPF library for Java. His most recent contribution is the new CPU Time Profiler in JDK 25.

    View all posts

New posts like these come out at least every two weeks, to get notified about new posts, follow me on BlueSky, Twitter, Mastodon, or LinkedIn, or join the newsletter:

Leave a Reply

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