Friday, December 26, 2025

Java 25: Stable Values

Java 25 introduces Stable Values, which are objects that hold immutable data. They let you initialise immutable fields lazily, while still allowing the JVM to treat them as constants, and thus perform the same optimisations (such as constant-folding) that are done for final fields. This is a preview language feature.

Consider the following example:

public class Controller {
    private final ExpensiveResource resource = new ExpensiveResource();
    
    public void process(String request) {
        resource.get(request);
    }
}

The problem here is that, since resource is a final field, it must be initialised eagerly, which means creating a Controller can be slow. It's also unnecessary to create the expensive resource if the process method is never called during the runtime of the application. In order to "defer immutability" and lazily initialise fields, we have to use complex workarounds such as the class-holder idiom, as shown below:

public class Controller {
    public static ExpensiveResource getResource() {
        class Holder {
            private static final ExpensiveResource RESOURCE =
                    new ExpensiveResource();
        }
        return Holder.RESOURCE;
    }

    public void process(String request) {
        getResource().get(request);
    }
}

This is where Stable Values come in.

Here is the same class, rewritten using a StableVaue:

public class Controller {
    private final StableValue<ExpensiveResource> resource = StableValue.of();

    public ExpensiveResource getResource() {
        return resource.orElseSet(() -> new ExpensiveResource());
    }

    public void process(String request) {
        getResource().get(request);
    }
}

Initially, the stable value holds no content. When the orElseSet method is invoked for the first time, the expensive resource is initialised and set into the stable value, and subsequent calls will simply return it. The orElseSet method guarantees that the provided lambda expression is evaluated only once, even when it is invoked concurrently.

A more convenient way to use stable values is via a Supplier instead, as shown below:

public class Controller {
    private final Supplier<ExpensiveResource> resource = 
        StableValue.supplier(() -> new ExpensiveResource());

    public void process(String request) {
        resource.get().get(request);
    }
}

Using a stable value supplier, rather than a stable value, is more readable because the declaration and initialisation of the resource field are now together.

Under the hood, a stable value is a non-final field annotated with the JDK-internal @Stable annotation. This tells the JVM that the field will never change after it is written. Due to this guarantee, the JVM can treat the value like a constant, provided that the reference to the stable value is final, and perform constant-folding optimisations, even through multiple layers of stable values.

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.