Stable Values in Java (JEP 502): Deferred Immutability with JVM Trust

Stable Values in Java
Explore Stable Values - a new Java 25 upcoming feature (JEP 502) enabling lazy initialization with immutable performance and JVM-level optimizations.

Java is evolving — and one of the more interesting additions in JDK 25 is Stable Values, introduced under JEP 502. The idea is to let you defer initialization of immutable data, yet still allow the JVM to treat those values as constants (and thus apply optimizations like constant folding). According to the JEP summary: “stable values are treated as constants by the JVM, enabling the same performance optimizations that are enabled by declaring a field final”. If, at the time you explore this new feature, its status is still preview, then you must explicitly enable preview features when compiling and running your code.

The Goals of the JEP 502 Specification

The JEP 502 specification defines what Stable Values are and how they should behave. The key goals include the following ones: 

  • Improve startup performance of Java applications by breaking up monolithic initialization of application state.
  • Decouple the time when a stable value is created from when it is initialized, while still retaining high performance.
  • Guarantee that stable values are initialized at most once, even in multithreaded programs, so that there is no race that leads to multiple or conflicting assignments.
  • Let user-level code safely enjoy constant-folding and other compiler/JVM optimizations. 

The JEP 502 specification is very clear regarding whether this new addition comes to replace the final modifier and specifically states that it isn’t. 

The Motivation for Using Stable Values

Immutable data is a powerful tool in multi-threaded programming: immutable objects can be safely shared without synchronization. In Java, final fields are the principal way to enforce immutability. However, the use of the final modifier has limitations:

  • final instance fields must be initialized in the constructor or at their declaration.

  • final static fields must be initialized in a static initializer or at declaration.

  • The textual order of their declaration influences their initialization order.

  • We lose flexibility. We cannot delay the computation of a value until it is actually needed, nor can we choose to have the initialization logic executed at a later point.

 

These constraints mean that many Java applications suffer from eager initialization overhead, particularly at startup, even for components that might never be actually used. Stable Values fill this gap: they let us defer the initialization but still provide the strong guarantee that once set, the value never changes. This enables us to structure our code so that expensive initialization happens lazily (on first use) rather than up front, without giving up the assumptions and optimizations that immutable final fields enjoy.  Under the hood, a stable value is backed by a non-final field annotated with JDK’s internal @Stable, signaling to the JVM that, despite being mutable at the language level, it should be trusted as immutable once initialized. This instructs the JVM to treat access to stable values as constant references, under certain conditions.

Simple Example

Share:

The Visitor Design Pattern

The Visitor Design Pattern

The visitor design pattern allows us to add operations to objects that already exist without modifying their classes and without extending them.

What are Anti Patterns?

Anti Patterns

Unlike design patterns, anti patterns just seem to be a solution. However, they are not a solution and they cause additional costs.

The Beauty of Code

Coding is Art! Developing Code That Works is Simple. Develop Code with Style is a Challenge!

Update cookies preferences