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 <[email protected]> on behalf of david Grajales <[email protected]> Sent: Friday, December 5, 2025 5:38 AM To: amber-dev <[email protected]>; [email protected] <[email protected]> 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
