bye stable values, enter lazy constants
Sergey Bylokhov
bylokhov at amazon.com
Mon Sep 29 16:28:44 UTC 2025
Hello Java Guru!
There is a very small and mostly cosmetic problem with the code examples in
https://openjdk.org/jeps/526. The modifiers "private/public final" are used inconsistently. In some
cases they are omitted, in others they are included. If they are added everywhere, some examples
become unnecessarily verbose. For instance:
private static final LazyConstant<OrderController> ORDERS = LazyConstant.of(OrderController::new)
**Some random thoughts:
The description of the JEP highlights two main issues: lazy initialization and constant-folding
optimizations. This is even reflected in the name of the proposed class LazyConstant. From the
user's perspective, knowing the type immediately conveys that the data will be initialized on demand
and that the JVM may propagate it as a constant where possible. But is this always true?
Consider the case:
private LazyConstant<OrderController> ORDERS = LazyConstant.of(...)
The value is initialized lazily, but can the JVM reliably inline ORDERS since it is not declared
final? If correct usage requires declaring the field as final, then the following looks somewhat
redundant, almost like writing "final-final"
private final LazyConstant<OrderController> ORDERS = LazyConstant.of(...)
It would be better if the new API could enforce these semantics regardless of whether the user adds
final. In other words, laziness and stability should be intrinsic properties of the construct.
Ideally something like:
private static lazy OrderController ORDERS = OrderController::new
or
private static lazy OrderController ORDERS = () -> new OrderController(...params...)
where the lazy modifier would imply both final and lazy initialization semantics.
Since introducing new keywords is not currently desirable, a practical alternative might be:
private static final Lazy<OrderController> ORDERS = OrderController::new
or
private static final Lazy<OrderController> ORDERS = () -> new OrderController(...params...)
In this case the Lazy class provides laziness and stability, while final ensures the reference
itself is constant and therefore optimizable by the JVM.
At this point one may ask why a lambda or method reference must be wrapped in Lazy<T> at all. We
already have a well-established abstraction for this: Supplier<T> and any other functional
interfaces. Perhaps all we need is a way to mark such suppliers as stable? For example:
private static final Supplier<OrderController> ORDERS = (() -> new OrderController(...)).stable();
The JEP is very performance and stability focused, but one important use case it tries to simplify
is the initialization of singletons. A well-known current idiom described in the JEP is:
public static Logger getLogger() {
class Holder {
private static final Logger LOGGER = Logger.create(...);
}
return Holder.LOGGER;
}
One great and useful property of this idiom is that the enclosing class's namespace is not polluted
with unnecessary fields.
By contrast, the new API encourages declaring fields directly at class scope. This introduces
potential problems. A field may be initialized and accessed directly, bypassing the accessor method:
private final LazyConstant<Logger> logger = LazyConstant.of(() ->
Logger.create(OrderController.class))
Or a field may remain unset until the accessor is executed, which could lead to exceptions if it is
accessed elsewhere prematurely:
private final LazyConstant<Logger> logger = LazyConstant.of()
A more natural solution could be to allow static lazy fields inside methods. This can be implemented
independently of this JEP and would remove singleton initialization from the scope of lazy
constants, addressing part of the criticism. For example:
public static Logger getLogger() {
static lazy/const/final Logger LOGGER = Logger.create(...);
return LOGGER;
}
at the end this will be just a syntactic sugar around:
private static final/lazy Logger LOGGER = Logger.create(...);
public static Logger getLogger() {
return LOGGER;
}
--
Best regards, Sergey.
On 9/29/25 02:15, Maurizio Cimadamore wrote:
> Hi all,
> Per and I have been completed another round of preview of the Stable
> Value API. As you might have noticed, the name changed (again!) to
> LazyConstant 🙂
>
> https://openjdk.org/jeps/526
>
> In many ways, the new API is more similar to the old ComputedConstant
> API, as the imperative methods are gone: the only way to create a lazy
> constant is by providing a lambda expression at construction.
>
> The reason behind the change is that, after performing extensive
> experiments with the StableValue API, we have learned that:
>
> * Most use cases could be expressed with a caching supplier. This meant
> that in most cases developers never needed to interact with StableValue
> -- which seemed odd.
> * For low-level cases, the imperative API we provided was not low-level
> enough, as it still added significant synchronization and boxing costs.
> This made the API unsuitable to use in performance critical code (such
> as the ClassFile API).
>
> For these reasons we have decided to shift the design center of the API
> back to the most common, functional use cases, which led us to this
> simpler API. That said, the quest for a more fundamental building block
> is not over: we now have a clearer idea on how to expose "stable" access
> to regular fields/array elements, using a _new_ var handle access mode.
> Initial experiments look indeed promising: such an approach doesn't
> require the allocation of intermediate abstractions, and allows clients
> to decide if they want to pay for synchronization or not.
>
> As always, we are very thankful for all the feedback we have received so
> far, and we look forward for some more!
>
> Cheers
> Maurizio
>
>
>
--
Best regards, Sergey.
More information about the leyden-dev
mailing list