Contents

Contents

JEP 526 - Stable Values Become Lazy Constants

JEP 526 - Stable Values Become Lazy Constants featured image

In Java 25, a new feature called Stable Values was introduced. Today, we will take a look at JEP-526, the continuation of working on that feature. It will be a preview feature, which means everything can be changed.

First, the most noticeable is the renaming of that feature to Lazy Constants. Personally, I think that is way more suitable for that functionality.

What are Lazy Constants

A LazyConstant <T> is a container that holds a single value of type T. Once assigned, that value becomes immutable. You can think of it as an eventually final value. It’s vital to notice that a reference to an object will be immutable. The object beneath can be changed.

Before Lazy Constants

Before Java 25/26, to achieve immutable data, we had to use the final keyword.

class Controller {
 private final EmailSender sender = new EmailSender();
}

There were some problems with this approach.

Whenever our code has a final field, it must be set eagerly, either initialized by a constructor or by having a static field. As a result, the application's startup may suffer. Not all fields are immediately needed, right?

So maybe we could remove the final keyword and do something like that:


class PetClinicController {

 private EmailSender sender = null;

 EmailSender getSender() {
   if (sender == null) {
     sender = new EmailSender();
   }
   return sender;
 }

 void adoptPet(User user, Pet pet) {
   // some logic here
   getSender().sendEmailTo(user, "You are great person!");
 }
}

It’s going to work, right? The startup will be faster. And yes, that's one way to solve this issue before Java 25/26. Unfortunately, it's not free.

  • Our sender is still mutable; we can assign a different value to this variable. However, we would need to use external tools to prevent us from doing that, or be very precise during code reviews.
  • Potential null pointer exception while accessing a field from another way than just using a getter.
  • It would be good to have “getSender” thread-safe.
  • But even when we do so, we prevent the JVM from optimizing access for this field. For example, using constant-folding.

Let's use some Lazy Constants

Using our example from before, let's migrate to the new Java.


class PetClinicController {

 private final LazyConstant<EmailSender> sender = LazyConstant.of(() -> new EmailSender());

 EmailSender getSender() {
   return sender.get();
 }

 void adoptPet(User user, Pet pet) {
   // some logic here
   getSender().sendEmailTo(user, "You are great person!");
 }
}

The code is very similar, but LazyConstant handles the nullability of EmailSender. Thanks to that, it’s impossible to call EmailSender without first calling a method to retrieve the value.

What has changed in Java 26?

LazyConstant became significantly smaller and more consistent than Stable Values were.

No of() constructor

There will no longer be an empty of() constructor. Right now, you need to provide the supplier in a method parameter. Just like we did in the example above.

Only one orElse method

Methods like orElseSet, setOrThrow, and trySet were replaced by just orElse method.
It’s important to understand how orElse method works.


LazyConstant<String> example = LazyConstant.of(() -> "Hello");
var result = example.orElse("Different hello");
System.out.println(result);

Our result here will be "Different hello" because LazyConstant wasn’t initialized.
We need to invoke .get() method to do so.

Stable Functions concept was completely removed

From Java 26, we will be able to use only stable collections.

We can also use unmodifiable collections together with stable values.
For now, we only have List and Map.

Java 25


List<Integer> list = StableValue.list(SIZE, INT_FUNCTION);
Map<Color, HowCute> map = StableValue.map(KEYS, CUTE_FUNCTION);

Java 26


List<Integer> list = List.ofLazy(SIZE,INT_FUNCTION);
Map<Color, HowCute> map = Map.ofLazy(KEYS, CUTE_FUNCTION);

Potential issues

LazyConstants are simple and useful constructs. Yet it’s worth noting the potential pitfalls of using them.

Serializable

It’s worth noticing that Lazy Constants do not support Serializable. It’s not mentioned in JEP, but it’s possible to find it here.

So, if you use serializable and want to replace all finals with Stable Values, you may encounter problems.

Not Using Final

Whenever we use LazyConstant, we need to remember the possibility of assigning a different value to this variable. Nothing prevents us from doing something like that (nothing but common sense, of course):


private LazyConstant<EmailSender> sender = LazyConstant.of(() -> new EmailSender());

void adoptPet(User user, Pet pet) {
 sender = null;
    // some logic here
 sender.get() //Null pointer exception
}

You should use final with all your LazyConstant, but you are not forced to do so. It’s a case of Optional<> once again.

So why didn’t they introduce a new keyword? Something like lazy, just like we have in different languages? Let's look into their motivation.

What is the goal of this JEP?

From this JEP, we learn that the author's goal was to improve the startup of Java applications by decoupling the creation of lazy constants from initialization. Making sure it will work in a multi-threaded environment and give us the possibility to use JVM optimizations like constant-folding.

Their goal is not to:

  • It is not a goal to enhance the Java programming language with a means to declare lazy fields.
  • It is not a goal to alter the semantics of final fields.

So even if I personally would love to have a lazy keyword, I’m glad that they introduced something that will help us optimize code. Can’t wait for the following updates.

Reviewed by: Szymon Winiarz

Blog Comments powered by Disqus.