[External] : Re: Feedback about LazyConstants API (JEP526)

Maurizio Cimadamore maurizio.cimadamore at oracle.com
Tue Dec 9 11:50:48 UTC 2025


An optional-returning tryGet is one of the possible ideas floating 
around, yes.

Maurizio

On 09/12/2025 11:37, Red IO wrote:
> You can implement orElse in combination with a test function that 
> returns a boolean rather the value is there already. I don't see 
> orElse as such a great primitive.
> At this point I want to advertise my tryGet suggestion that returns an 
> Option again as I think it would satisfy the people wanting orElse 
> without the confusion orElse created in this discussion alone.
> Also in this sense tryGet is the more pure form of the "primitive" as 
> it returns basically a tuple of the value and a boolean rather the 
> value is there. Which is pretty much the entire state the LazyConstant 
> is carrying without any transformation of information.
>
> Great regards
> RedIODev
>
>
> On Tue, Dec 9, 2025, 11:25 Maurizio Cimadamore 
> <maurizio.cimadamore at oracle.com> wrote:
>
>     I agree with most of the conclusions in this thread.
>
>
>     One small nit is that, in reality, `orElse` is a "primitive" in
>     disguise. E.g. you can implement `get` in terms of `orElse` but
>     not the other way around (unless you are willing to do _two_
>     accessed to the underlying value). So, while we could drop it, we
>     would also lose something (which is why we decided to keep it, at
>     least for now).
>
>
>     Maurizio
>
>
>
>     On 08/12/2025 12:31, Per-Ake Minborg wrote:
>>     So, it is nice that folks seem to agree that
>>     |LazyConstant| should only compute and initialize its contents
>>     from the Supplier/lambda given at declaration time. The
>>     |orElse| method seems to blur the contours of |LazyConstant| ,
>>     and so, as previously said, we might consider removing the method
>>     altogether in the next preview.
>>
>>     It is also a fact that many have identified a need for "something
>>     else more low-level" that supports a more imperative programming
>>     model when working with constants that are lazily set. We do not
>>     rule out that such a thing might appear in a future JDK version.
>>
>>     Best, Per
>>
>>     Confidential- Oracle Internal
>>     ------------------------------------------------------------------------
>>     *From:* David Alayachew <davidalayachew at gmail.com>
>>     <mailto:davidalayachew at gmail.com>
>>     *Sent:* Friday, December 5, 2025 2:51 PM
>>     *To:* Red IO <redio.development at gmail.com>
>>     <mailto:redio.development at gmail.com>
>>     *Cc:* david Grajales <david.1993grajales at gmail.com>
>>     <mailto:david.1993grajales at gmail.com>; Per-Ake Minborg
>>     <per-ake.minborg at oracle.com> <mailto:per-ake.minborg at oracle.com>;
>>     amber-dev <amber-dev at openjdk.org> <mailto:amber-dev at openjdk.org>;
>>     core-libs-dev <core-libs-dev at openjdk.org>
>>     <mailto:core-libs-dev at openjdk.org>
>>     *Subject:* [External] : Re: Feedback about LazyConstants API
>>     (JEP526)
>>     Caveat -- I have only used the Java 25 version of this library.
>>
>>     I agree that the name orElse() is not intuitive. It was made more
>>     intuitive by the existence of orElseSet(). In its absence,
>>     changing the name makes sense.
>>
>>     Though, I'm definitely open to just removing the method. This is
>>     easy enough to accomplish ourselves. Would prefer a rename though.
>>
>>     On Fri, Dec 5, 2025, 8:32 AM Red IO <redio.development at gmail.com>
>>     wrote:
>>
>>         Hi David,
>>         As par already said the orElse method doesn't initializes the
>>         LazyConstant.
>>         It just checks rather the value is init and if not calls the
>>         supplier to get a substitute for the missing constant.
>>         Example:
>>         LazyConstant<String> x = LazyConstant.of(() -> "Const");
>>         var uninit1 = x.orElse(() -> "substitute 1");
>>         var uninit2 = x.orElse(() -> "substitute 2");
>>         var init1 = x.get();
>>         var init2 = x.orElse(() -> "substitute 3");
>>         uninit1 and uninit2 get the substitute 1/2
>>         And init1 and init2 get Const.
>>
>>         This is surprising if you expect it to be a way to init it
>>         with an alternative value.
>>
>>         My suggestion would to make the separation clear and allow
>>         for another use case by spliting this api in 2 parts:
>>         One class LazyConstant
>>         Takes a Supplier in static factory and exposes get()
>>
>>         And
>>         Class LazyInit
>>         Which takes no arguments in the static factory and takes a
>>         supplier in the get method that gets called when get is
>>         called for the first time.
>>         In this case the source for the constant can be any piece of
>>         code that has access to the LazyConstant. This might be
>>         desired in some cases. In cases where it's not the other
>>         version can be used.
>>
>>         This split makes it clear from which context the constant is
>>         initialized from (consumer or at declaration)
>>
>>         Mixing those 2 or having methods that appear to do this is
>>         rather confusing.
>>
>>
>>
>>         One solution for the "i might not want to init the constant"
>>         case the "orElse" method is meant to be is to have a method
>>         "tryGet" which returns Optional instead. This makes it clear
>>         that the value might not be there and is not initialized when
>>         calling the method. Nobody expects to init the constant when
>>         calling orElse on a returned Optional.
>>
>>         My 2 suggestions here are completely independent and should
>>         be viewed as such.
>>
>>         Great regards
>>         RedIODev
>>
>>         On Fri, Dec 5, 2025, 13:55 david Grajales
>>         <david.1993grajales at gmail.com> wrote:
>>
>>             HI Per. I pleasure to talk with you.
>>
>>             You are right about one thing but this actually makes the
>>             API less intuitive and harder to read and reason about.
>>
>>             LazyConstant<String> foo = LazyConstant.of(() -> "hello");
>>
>>             void main() {
>>                 if (someCondition()) {// asume false
>>                     foo.get();
>>                 }
>>                 foo.orElse("hello2"); // ...
>>
>>                 println(foo.get()); // This prints "hello"
>>             }
>>
>>             But if one assigns foo.orElse("hello2") to a variable,
>>             the variable actually gets the "hello2" value.
>>
>>             void main() {
>>                 if (someCondition()) {// asume false
>>                     foo.get();
>>                 }
>>                 var res = foo.orElse("hello2"); // ...
>>                 var res2 = foo.orElse("hello3");
>>                 println(res); // This prints "hello2"
>>                 println(res2);//This prints "hello3"
>>             }
>>
>>             This is actually even more confusing and makes the API
>>             more error prone. I personally think once initialized the
>>             lazy constant should always return the same value (maybe
>>             through the .get() method only), and there should not be
>>             any possibility of getting a different values from the
>>             same instance either in the .of() static method or in any
>>             hypothetical instance method for conditional downstream
>>             logic.  I guess one could achieve the latter with the
>>             static factory method through something like this
>>             (although less elegant)
>>
>>             private class Bar{
>>                 private final LazyConstant<String> foo;
>>                 private Bar(Some some){
>>
>>                     if(some.condition){
>>                         foo = LazyConstant.of(() -> "hello");
>>                     }else {
>>                         foo = LazyConstant.of(() -> "hello2");
>>                     }
>>                 }
>>             }
>>
>>             Thank you for reading. This is all I have to report.
>>
>>             Best regards.
>>
>>
>>
>>             El vie, 5 dic 2025 a la(s) 6:05 a.m., Per-Ake Minborg
>>             (per-ake.minborg at oracle.com) escribió:
>>
>>                 Hi David,
>>
>>                 Thank you for trying out LazyConstant and providing
>>                 feedback. That is precisely what previews are for!
>>
>>                 If you take a closer look at the specification of
>>                 |LazyConstant::orElse,| it says that the method will
>>                 /never trigger initialization./ And so, you
>>                 /can/ actually be sure that in your first example,
>>                 |foo| is always initialized to "hello" (if ever
>>                 initialized). It is only if foo is not initialized
>>                 that the method will return "hello2" (again, without
>>                 initializing foo). This is similar to how
>>                 |Optional| works.
>>
>>                 It would be possible to entirely remove the
>>                 |orElse()| method from the API, and in the rare cases
>>                 where an equivalent functionality is called for, rely
>>                 on |LazyConstant::isInitialized| instead.
>>
>>                 Best, Per
>>
>>
>>                 Confidential- Oracle Internal
>>                 ------------------------------------------------------------------------
>>                 *From:* amber-dev <amber-dev-retn at openjdk.org> on
>>                 behalf of david Grajales <david.1993grajales at gmail.com>
>>                 *Sent:* Friday, December 5, 2025 5:38 AM
>>                 *To:* amber-dev <amber-dev at openjdk.org>;
>>                 core-libs-dev at openjdk.org <core-libs-dev at openjdk.org>
>>                 *Subject:* Feedback about LazyConstants API (JEP526)
>>                 Dear Java Dev Team,
>>
>>                  I am writing to provide feedback and two specific
>>                 observations regarding the LazyConstant API, which is
>>                 currently a preview feature in OpenJDK 26.
>>
>>                  I appreciate the API's direction and I think it's a
>>                 good improvement compared to its first iteration;
>>                 however, I see potential for improved expressiveness,
>>                 particularly in conditional scenarios.
>>
>>
>>                 *1. Proposal: Zero-Parameter `LazyConstant.of()`
>>                 Overload:*
>>
>>                 Currently, the mandatory use of a factory method
>>                 receiving a `Supplier` (due to the lack of a public
>>                 constructor) can obscure the expressiveness of
>>                 conditional or multiple-value initialization paths.
>>                 **The Issue:** When looking at the declaration:
>>
>>                 LazyConstant<String> foo = LazyConstant.of(() ->
>>                 "hello");
>>
>>                 the code gives the strong, immediate impression that
>>                 the value is *always* initialized to |"hello"|. This
>>                 makes it difficult to infer that the constant might
>>                 ultimately resolve to an alternative value set later
>>                 via |orElse()| or another conditional path,
>>                 especially when skimming the code:
>>
>>                 LazyConstant<String> foo = LazyConstant.of(() ->
>>                 "hello"); // When skimming the code it's not always
>>                 obvious that this may not be the actual value
>>                 void main() {
>>                 if (someCondition()) {
>>                           foo.get(); // Trigger initialization to
>>                 "hello"
>>                  }
>>                 // If someCondition is false, the final value of foo
>>                 is determined here:
>>                 var res1 = foo.orElse("hello2"); // ...
>>                 }
>>
>>                 *My Suggestion:* I propose introducing a
>>                 *zero-parameter overloaded static factory method* |of()|:
>>
>>                 LazyConstant<String> foo = LazyConstant.of();
>>
>>                 This form explicitly communicates that the constant
>>                 is initialized to an *unresolved* state, suggesting
>>                 that the value will be determined downstream by the
>>                 first invocation of an initialization/computation method.
>>
>>                 LazyConstant<String> foo = LazyConstant.of(); //
>>                 Clearly unresolved
>>                 void main() {
>>                 if (someCondition()) {
>>                       foo.orElse("hello");
>>                  }
>>                 var res1 = foo.orElse("hello2"); // ...
>>                 }
>>
>>                 This is specially useful for clarity when one has
>>                 conditional initialization in places such as the
>>                 constructor of a class. For example
>>
>>                 private class Bar{
>>                     LazyConstant<String> foo = LazyConstant.of();
>>                     private Bar(Some some){
>>                         if(some.condition()){
>>                             foo.orElse("foo");
>>                         }
>>                         foo.orElse("foo2");
>>                     }
>>
>>                     String computeValue() {
>>                         return "hello";
>>                     }
>>
>>                     String computeValue2(){
>>                         return "hello2";
>>                     }
>>                 }
>>
>>
>>                       2. Method Naming Suggestion and and supplier in
>>                       instance method for consistency in the API
>>
>>                 My second, much more minor observation relates to the
>>                 instance method |orElse(T t)|.
>>
>>                 While |orElse| fits a retrieval pattern, I personally
>>                 feel that *|compute|* or *|computeIfAbsent|* would
>>                 better express the intent of this method, as its
>>                 primary function is not just to retrieve, but to
>>                 trigger the computation and *set the final value* of
>>                 the constant if it is currently uninitialized. Also,
>>                 as the factory of() has a supplier i think this
>>                 instance method should also receive a Supplier, This
>>                 not only keeps the API consistent in the usage but
>>                 makes more ergonomic the declaration of complex
>>                 initialization logic inside the method.
>>
>>
>>                 private class Bar{
>>                 LazyConstant<InitParams> foo =
>>                 LazyConstant.of(InitParam::default); // Under the
>>                 current API this is mandatory but in reality the
>>                 value is set in the constructor, default is never
>>                 really used.
>>                     private Bar(Some some){
>>                  foo.compute(some::executeCallToCacheDBAndBringInitializationParams)
>>                 //Real configuration happens here
>>
>>                     }
>>                 }
>>
>>                 This last it's very common for initialization of
>>                 configuration classes and singletons.
>>
>>
>>                 Thank you so much for your attention, I hope you find
>>                 this feedback useful.
>>
>>                 Always yours. David Grajales
>>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/amber-dev/attachments/20251209/077decae/attachment-0001.htm>


More information about the amber-dev mailing list