Hello David, and thank you for getting back to us with feedback on the Lazy 
Constants API.

In the second preview, LazyConstant can not be set imperatively. In other 
words, we have removed the old StableValue methods: trySet(), setOrThrow() and 
orElseSet() as they were generally difficult to use and were prone to creating 
boxing, lambda capturing, and locking issues, which could negatively impact 
both performance, code readability, and application maintenance costs.

Instead, an underlying computing function must now be given at construction 
time. The various use cases we have identified indicate that this 
simplification of the API still covers a vast majority of those cases. However, 
there are other, more complicated cases that are not easily supported by 
LazyConstant and for which a more lower-level access capability would be 
appropriate.  We are planning to address this in future works, separate from 
LazyConstant. We will add that piece of information to the JEP, so thank you 
for identifying this. With respect to adding separate language constructs, such 
as a lazy keyword, the bar for this is extremely high and would have to be 
motivated and gauged against other potential language improvements, such as 
evolving pattern matching.

Note that LazyConstant now has a get() method, and that it also implements 
Supplier for easy integration with existing code.

Here is what your first example could look like with the new API:

```

        private static final LazyConstant<MySingleton> MESSAGE =
                LazyConstant.of(() -> new MySingleton("Hello Java"));

        void main(){
            IO.println(MESSAGE.get().message());
        }

        private record MySingleton(String message){ }

```

This will provide constant folding of the `MESSAGE.get().message()` sequence in 
case the JIT compiler decides to compile it.

I think the other examples you show (albeit I didn't fully get how they were 
supposed to work) would have issues regardless of whether there were language 
or library support for lazy computation, because the initialization logic is 
not known at declaration time. Some other languages (like Kotlin's `lateinit` 
[1]) have constructs that would seem to fit at a first glance, but they come 
with other severe semantic limitations (such as lack of thread-safety, lack of 
support for primitive types, and no constant folding). We want to do better and 
will, as previously said, address this down the road.

Thanks again for your feedback, David.

Best, Per Minborg

[1] https://medium.com/@anandgaur2207/lateinit-vs-lazy-in-kotlin-9608fb286fc6
[https://miro.medium.com/v2/resize:fit:1200/0*BqZDTJExg-RoB9qm.png]<https://medium.com/@anandgaur2207/lateinit-vs-lazy-in-kotlin-9608fb286fc6>
Lateinit vs Lazy in Kotlin - 
Medium<https://medium.com/@anandgaur2207/lateinit-vs-lazy-in-kotlin-9608fb286fc6>
Lateinit vs Lazy in Kotlin When building Android apps in Kotlin, you often need 
to initialize variables, but the exact moment of initialization may vary 
depending on your app’s requirements ...
medium.com

________________________________
From: amber-dev <[email protected]> on behalf of david Grajales 
<[email protected]>
Sent: Wednesday, September 24, 2025 4:02 AM
To: amber-dev <[email protected]>
Subject: Feedback on LazyConstants (formerly StableValues)


Dear Amber team,

I hope this message finds you well. First of all, I want to extend my gratitude 
for all your hard work in continuously improving the Java platform — it is very 
much appreciated.

I have been experimenting with the StableValues API (now renamed LazyConstants) 
and attempting to use it in place of some of my older automation scripts for 
personal projects. While doing so, I noticed that the current design feels a 
bit rigid in certain scenarios, and this may indicate that a keyword or 
annotation-based solution could be a better fit.

Let me illustrate with a simple example. A common case of deferred 
initialization is the lazy singleton pattern:

void main(){
    var message = MySingleton.getInstance("Hello java").getMessage();
    println(message);
}

private static class MySingleton{
    private static MySingleton instance;
    private final String message;

    private MySingleton(String message) {
        this.message = message;
    }

    public static  MySingleton getInstance(String message) {
        if (instance == null) {
            instance = new MySingleton(message);
        }
        return instance;
    }
    public String getMessage() {
        return message;
    }
}


While this works, the result feels neither more concise nor more readable. The 
code is now cluttered by the API’s requirements rather than improved by them.


public class MySingleton2 {
    private static final StableValue<MySingleton2> instance = StableValue.of();
    private final String message;

    private MySingleton2(String message) {
        this.message = message;
    }

    public static MySingleton2 getInstance(String message){
        return instance.orElse(new MySingleton2(message));
    }

    public String getMessage(){
        return message;
    }
}

The issue here is that the current API does not offer a way to lazily capture 
constructor parameters — for example, through a Function or Supplier-based 
variant. Something like this would feel more natural:

public  <T, R>StableValue<R> createLazy(T t, Function<T, R> underlying){
    return StableValue.of(underlying.apply(t));
}

final StableValue<MySingleton> instanceLazy = createLazy("Hello", 
MySingleton::getInstance);

Now let's take a look to the case where the message field is lazy too.

public class MySingleton3 {
    private static final LazyConstant<MySingleton3> instance =
            LazyConstant.of(ew MySingleton3());

    private LazyConstant<String> message;

    private MySingleton3() {
        // constructor doesn't accept the dynamic value in this pattern,
        // so the message must be initialized separately.
    }


    public static MySingleton3 getInstance(String value) {
        // obtain the singleton (or create one if absent)
        MySingleton3 s = instance.orElse(new MySingleton3());

        // awkward: we must initialise the instance's lazy message here to 
capture `value`
        if (s.message == null) {
            s.message = LazyConstant.of(s.computeMessage(value));
        }

        return s;
    }

    public String getMessage() {
        // we assume getInstance(...) has already set `message`; read it with 
orElse(null)
        // (using `null` here to avoid any extra computation as orElse takes a 
concrete value).
        return message.orElse(null);
    }

    private String computeMessage(String value) {
        return "Hello " + value;
    }

    public static void main(String[] args) {
        // prints "Computing message..." once, then "Hello Java"
       println(MySingleton3.getInstance("Java").getMessage());

    }
}

As you can see, there is no real improvement from a code perspective — in fact, 
the resulting implementation is harder to reason about. The mental model for 
the developer becomes more complex, since they must explicitly manage when and 
how values are captured. Additionally, the lack of a simple get() method 
(having only orElse(...) and orElseThrow(...)) makes the code more verbose and 
slightly redundant, especially in cases where you know the value has already 
been initialized.

Now, the issue is not the lack of this or other methods. The issue relies on 
the infinite possible combinations that makes the design of a simple but 
flexible enough API a hard to achieve task. Seeing this limitation and the past 
limitations I have already shared in this mailing list, i am not sure if an API 
based approach is the best solution. I understand the good thing about API 
based features are how easy they are to grow, at least compared to a language 
level feature, but the current approach i fear may lead to an ever expanding 
API that would never be enough.


Considering this, and similar concerns I have previously shared here, I wonder 
if an API-based approach is really the right long-term solution. While 
API-based features are easier to evolve than language features, I worry that 
this approach could lead to an ever-expanding API that still never quite covers 
all the practical cases developers face. My current impression is that the API 
gets in the way more than it helps. Java already provides more natural ways to 
achieve lazy initialization, and LazyConstants ends up feeling like a low-level 
optimization, ceremony aimed at squeezing out marginal performance gains that 
may not even justify the added complexity.

I would love to hear your thoughts, pointing me to a better direction about how 
to use the API for this kind of cases. Is there something that i am not seeing?

Thank you for your time and for continuing to improve the Java platform.

Best regards and always yours: David Grajales Cardenas.

Reply via email to