T - type of the contentspublic sealed interface StableValue<T>
StableValue is a preview API of the Java platform. A StableValue<T> is typically created using the factory method StableValue.of(). When created this way, the stable value is unset, which means it holds no contents. Its contents, of type T, can be set by calling trySet(), setOrThrow(), or orElseSet(). Once set, the contents can never change and can be retrieved by calling orElseThrow() , orElse(), or orElseSet().
Consider the following example where a stable value field "logger" is a shallowly immutable holder of contents of type Logger and that is initially created as unset, which means it holds no contents. Later in the example, the state of the "logger" field is checked and if it is still unset, the contents is set:
public class Component {
// Creates a new unset stable value with no contents
private final StableValue<Logger> logger = StableValue.of();
private Logger getLogger() {
if (!logger.isSet()) {
logger.trySet(Logger.create(Component.class));
}
return logger.orElseThrow();
}
public void process() {
getLogger().info("Process started");
// ...
}
}
If getLogger() is called from several threads, several instances of Logger might be created. However, the contents can only be set at most once meaning the first writer wins.
In order to guarantee that, even under races, only one instance of Logger is ever created, the orElseSet() method can be used instead, where the contents are lazily computed, and atomically set, via a supplier. In the example below, the supplier is provided in the form of a lambda expression:
public class Component {
// Creates a new unset stable value with no contents
private final StableValue<Logger> logger = StableValue.of();
private Logger getLogger() {
return logger.orElseSet( () -> Logger.create(Component.class) );
}
public void process() {
getLogger().info("Process started");
// ...
}
}
The getLogger() method calls logger.orElseSet() on the stable value to retrieve its contents. If the stable value is unset, then orElseSet() evaluates the given supplier, and sets the contents to the result; the result is then returned to the client. In other words, orElseSet() guarantees that a stable value's contents is set before it returns.
Furthermore, orElseSet() guarantees that out of one or more suppliers provided, only at most one is ever evaluated, and that one is only ever evaluated once, even when logger.orElseSet() is invoked concurrently. This property is crucial as evaluation of the supplier may have side effects, for example, the call above to Logger.create() may result in storage resources being prepared.
public class Component {
private final Supplier<Logger> logger =
StableValue.supplier( () -> Logger.getLogger(Component.class) );
public void process() {
logger.get().info("Process started");
// ...
}
}
Component can obtain the logger object directly from the stable supplier, without having to go through an accessor method like getLogger(). A stable int function is a function that takes an int parameter and uses it to compute a result that is then cached by the backing stable value storage for that parameter value. A stable IntFunction is created via the StableValue.intFunction() factory. Upon creation, the input range (i.e. [0, size)) is specified together with an underlying IntFunction which is invoked at most once per input value. In effect, the stable int function will act like a cache for the underlying IntFunction:
final class PowerOf2Util {
private PowerOf2Util() {}
private static final int SIZE = 6;
private static final IntFunction<Integer> UNDERLYING_POWER_OF_TWO =
v -> 1 << v;
private static final IntFunction<Integer> POWER_OF_TWO =
StableValue.intFunction(SIZE, UNDERLYING_POWER_OF_TWO);
public static int powerOfTwo(int a) {
return POWER_OF_TWO.apply(a);
}
}
int result = PowerOf2Util.powerOfTwo(4); // May eventually constant fold to 16 at runtime
PowerOf2Util.powerOfTwo() function is a partial function that only allows a subset [0, 5] of the underlying function's UNDERLYING_POWER_OF_TWO input range. A stable function is a function that takes a parameter (of type T) and uses it to compute a result (of type R) that is then cached by the backing stable value storage for that parameter value. A stable function is created via the StableValue.function() factory. Upon creation, the input Set is specified together with an underlying Function which is invoked at most once per input value. In effect, the stable function will act like a cache for the underlying Function:
class Log2Util {
private Log2Util() {}
private static final Set<Integer> KEYS =
Set.of(1, 2, 4, 8, 16, 32);
private static final UnaryOperator<Integer> UNDERLYING_LOG2 =
i -> 31 - Integer.numberOfLeadingZeros(i);
private static final Function<Integer, Integer> LOG2 =
StableValue.function(KEYS, UNDERLYING_LOG2);
public static int log2(int a) {
return LOG2.apply(a);
}
}
int result = Log2Util.log2(16); // May eventually constant fold to 4 at runtime
Log2Util.log2() function is a partial function that only allows a subset {1, 2, 4, 8, 16, 32} of the underlying function's UNDERLYING_LOG2 input range. final class PowerOf2Util {
private PowerOf2Util() {}
private static final int SIZE = 6;
private static final IntFunction<Integer> UNDERLYING_POWER_OF_TWO =
v -> 1 << v;
private static final List<Integer> POWER_OF_TWO =
StableValue.list(SIZE, UNDERLYING_POWER_OF_TWO);
public static int powerOfTwo(int a) {
return POWER_OF_TWO.get(a);
}
}
int result = PowerOf2Util.powerOfTwo(4); // May eventually constant fold to 16 at runtime
Similarly, a stable map is an unmodifiable map whose keys are known at construction. The stable map values are computed when they are first accessed, using a provided Function:
class Log2Util {
private Log2Util() {}
private static final Set<Integer> KEYS =
Set.of(1, 2, 4, 8, 16, 32);
private static final UnaryOperator<Integer> UNDERLYING_LOG2 =
i -> 31 - Integer.numberOfLeadingZeros(i);
private static final Map<Integer, INTEGER> LOG2 =
StableValue.map(CACHED_KEYS, UNDERLYING_LOG2);
public static int log2(int a) {
return LOG2.get(a);
}
}
int result = Log2Util.log2(16); // May eventually constant fold to 4 at runtime
Foo and a Bar instance (that is dependent on the Foo instance) are lazily created, both of which are held by stable values: public final class DependencyUtil {
private DependencyUtil() {}
public static class Foo {
// ...
}
public static class Bar {
public Bar(Foo foo) {
// ...
}
}
private static final Supplier<Foo> FOO = StableValue.supplier(Foo::new);
private static final Supplier<Bar> BAR = StableValue.supplier(() -> new Bar(FOO.get()));
public static Foo foo() {
return FOO.get();
}
public static Bar bar() {
return BAR.get();
}
}
bar() will create the Bar singleton if it is not already created. Upon such a creation, the dependent Foo will first be created if the Foo does not already exist. Another example, which has a more complex dependency graph, is to compute the Fibonacci sequence lazily:
public final class Fibonacci {
private Fibonacci() {}
private static final int MAX_SIZE_INT = 46;
private static final IntFunction<Integer> FIB =
StableValue.intFunction(MAX_SIZE_INT, Fibonacci::fib);
public static int fib(int n) {
return n < 2
? n
: FIB.apply(n - 1) + FIB.apply(n - 2);
}
}
FIB and Fibonacci::fib recurse into each other. Because the stable int function FIB caches intermediate results, the initial computational complexity is reduced from exponential to linear compared to a traditional non-caching recursive fibonacci method. Once computed, the VM is free to constant-fold expressions like Fibonacci.fib(5). The fibonacci example above is a directed acyclic graph (i.e., it has no circular dependencies and is therefore a dependency tree):
___________fib(5)____________
/ \
____fib(4)____ ____fib(3)____
/ \ / \
fib(3) fib(2) fib(2) fib(1)
/ \ / \ / \
fib(2) fib(1) fib(1) fib(0) fib(1) fib(0)
The at-most-once write operation on a stable value that succeeds (e.g. trySet()) happens-before any successful read operation (e.g. orElseThrow()). A successful write operation can be either:
trySet(Object) that returns true,setOrThrow(Object) that does not throw, ororElseSet(Supplier) that successfully runs the supplierorElseThrow() that does not throw,orElse(other) that does not return the other valueorElseSet(Supplier) that does not throw, orisSet() that returns true
The method orElseSet(Supplier) guarantees that the provided Supplier is invoked successfully at most once, even under race. Invocations of orElseSet(Supplier) form a total order of zero or more exceptional invocations followed by zero (if the contents were already set) or one successful invocation. Since stable functions and stable collections are built on top of the same principles as orElseSet() they too are thread safe and guarantee at-most-once-per-input invocation.
static final field). Stable functions and collections are built on top of StableValue. As such, they might also be eligible for the same JVM optimizations as for StableValue.StableValue are free to synchronize on this and consequently, it should be avoided to (directly or indirectly) synchronize on a StableValue. Hence, synchronizing on this may lead to deadlock. Except for a StableValue's contents itself, an orElse(other) parameter, and an equals(obj) parameter; all method parameters must be non-null or a NullPointerException will be thrown.
StableValue is mainly intended to be a non-public field in a class and is usually neither exposed directly via accessors nor passed as a method parameter. Stable functions and collections make reasonable efforts to provide Object.toString() operations that do not trigger evaluation of the internal stable values when called. Stable collections have Object.equals(Object) operations that try to minimize evaluation of the internal stable values when called.
As objects can be set via stable values but never removed, this can be a source of unintended memory leaks. A stable value's contents are strongly reachable. Be advised that reachable stable values will hold their set contents until the stable value itself is collected.
A StableValue that has a type parameter T that is an array type (of arbitrary rank) will only allow the JVM to treat the array reference as a stable value but not its components. Instead, a a stable list of arbitrary depth can be used, which provides stable components. More generally, a stable value can hold other stable values of arbitrary depth and still provide transitive constantness.
Stable values, functions, and collections are not Serializable.
| Modifier and Type | Method | Description |
|---|---|---|
boolean |
equals |
Returns true if this == obj, false otherwise. |
static <T, |
function |
Returns a new stable Function. |
int |
hashCode() |
Returns the identity hash code of this object. |
static <R> IntFunction |
intFunction |
Returns a new stable IntFunction. |
boolean |
isSet() |
Returns true if the contents is set, false otherwise. |
static <E> List |
list |
Returns a new stable list with the provided size. |
static <K, |
map |
Returns a new stable map with the provided keys. |
static <T> StableValuePREVIEW |
of() |
Returns a new unset stable value. |
static <T> StableValuePREVIEW |
of |
Returns a new pre-set stable value with the provided contents. |
T |
orElse |
Returns the contents if set, otherwise, returns the provided other value. |
T |
orElseSet |
Returns the contents; if unset, first attempts to compute and set the contents using the provided supplier. |
T |
orElseThrow() |
Returns the contents if set, otherwise, throws NoSuchElementException. |
void |
setOrThrow |
Sets the contents of this StableValue to the provided contents, or, if already set, throws IllegalStateException. |
static <T> Supplier |
supplier |
Returns a new stable supplier. |
boolean |
trySet |
Tries to set the contents of this StableValue to the provided contents. |
boolean trySet(T contents)
contents. The contents of this StableValue can only be set once, implying this method only returns true once. When this method returns, the contents of this StableValue is always set.
contents - to settrue if the contents of this StableValue was set to the provided contents, false otherwiseIllegalStateException - if a supplier invoked by orElseSet(Supplier) recursively attempts to set this stable value by calling this method directly or indirectly.T orElse(T other)
other value.other - to return if the contents is not setother valueT orElseThrow()
NoSuchElementException.NoSuchElementException
NoSuchElementException - if no contents is setboolean isSet()
true if the contents is set, false otherwise.true if the contents is set, false otherwiseT orElseSet(Supplier<? extends T> supplier)
supplier. The provided supplier is guaranteed to be invoked at most once if it completes without throwing an exception. If this method is invoked several times with different suppliers, only one of them will be invoked provided it completes without throwing an exception.
If the supplier throws an (unchecked) exception, the exception is rethrown and no contents is set. The most common usage is to construct a new object serving as a lazily computed value or memoized result, as in:
Value v = stable.orElseSet(Value::new);
When this method returns successfully, the contents is always set.
The provided supplier will only be invoked once even if invoked from several threads unless the supplier throws an exception.
supplier - to be used for computing the contents, if not previously setsupplier
IllegalStateException - if the provided supplier recursively attempts to set this stable value.void setOrThrow(T contents)
contents, or, if already set, throws IllegalStateException. When this method returns (or throws an exception), the contents is always set.
contents - to setIllegalStateException - if the contents was already setIllegalStateException - if a supplier invoked by orElseSet(Supplier) recursively attempts to set this stable value by calling this method directly or indirectly.int hashCode()
this object.hashCode in class Object
this objectstatic <T> StableValuePREVIEW<T> of()
An unset stable value has no contents.
T - type of the contentsstatic <T> StableValuePREVIEW<T> of(T contents)
contents.T - type of the contentscontents - to setcontents
static <T> Supplier<T> supplier(Supplier<? extends T> underlying)
The returned supplier is a caching supplier that records the value of the provided underlying supplier upon being first accessed via the returned supplier's get() method.
The provided underlying supplier is guaranteed to be successfully invoked at most once even in a multi-threaded environment. Competing threads invoking the returned supplier's get() method when a value is already under computation will block until a value is computed or an exception is thrown by the computing thread. The competing threads will then observe the newly computed value (if any) and will then never execute the underlying supplier.
If the provided underlying supplier throws an exception, it is rethrown to the initial caller and no contents is recorded.
If the provided underlying supplier recursively calls the returned supplier, an IllegalStateException will be thrown.
T - the type of results supplied by the returned supplierunderlying - supplier used to compute a cached valuestatic <R> IntFunction<R> intFunction(int size, IntFunction<? extends R> underlying)
The returned function is a caching function that, for each allowed int input, records the values of the provided underlying function upon being first accessed via the returned function's apply() method. If the returned function is invoked with an input that is not in the range [0, size), an IllegalArgumentException will be thrown.
The provided underlying function is guaranteed to be successfully invoked at most once per allowed input, even in a multi-threaded environment. Competing threads invoking the returned function's apply() method when a value is already under computation will block until a value is computed or an exception is thrown by the computing thread.
If invoking the provided underlying function throws an exception, it is rethrown to the initial caller and no contents is recorded.
If the provided underlying function recursively calls the returned function for the same input, an IllegalStateException will be thrown.
R - the type of results delivered by the returned IntFunctionsize - the upper bound of the range [0, size) indicating the allowed inputsunderlying - IntFunction used to compute cached valuesIllegalArgumentException - if the provided size is negative.static <T,R> Function<T,R> function(Set<? extends T> inputs, Function<? super T, ? extends R> underlying)
The returned function is a caching function that, for each allowed input in the given set of inputs, records the values of the provided underlying function upon being first accessed via the returned function's apply() method. If the returned function is invoked with an input that is not in inputs, an IllegalArgumentException will be thrown.
The provided underlying function is guaranteed to be successfully invoked at most once per allowed input, even in a multi-threaded environment. Competing threads invoking the returned function's apply() method when a value is already under computation will block until a value is computed or an exception is thrown by the computing thread.
If invoking the provided underlying function throws an exception, it is rethrown to the initial caller and no contents is recorded.
If the provided underlying function recursively calls the returned function for the same input, an IllegalStateException will be thrown.
T - the type of the input to the returned FunctionR - the type of results delivered by the returned Functioninputs - the set of (non-null) allowed input valuesunderlying - Function used to compute cached valuesNullPointerException - if the provided set of inputs contains a null element.static <E> List<E> list(int size, IntFunction<? extends E> mapper)
size. The returned list is an unmodifiable list with the provided size. The list's elements are computed via the provided mapper when they are first accessed (e.g. via List::get).
The provided mapper function is guaranteed to be successfully invoked at most once per list index, even in a multi-threaded environment. Competing threads accessing an element already under computation will block until an element is computed or an exception is thrown by the computing thread.
If invoking the provided mapper function throws an exception, it is rethrown to the initial caller and no value for the element is recorded.
Any subList or List.reversed() views of the returned list are also stable.
The returned list and its subList or List.reversed() views implement the RandomAccess interface.
The returned list is unmodifiable and does not implement the optional operations in the List interface.
If the provided mapper recursively calls the returned list for the same index, an IllegalStateException will be thrown.
E - the type of elements in the returned listsize - the size of the returned listmapper - to invoke whenever an element is first accessed (may return null)size
IllegalArgumentException - if the provided size is negative.static <K,V> Map<K,V> map(Set<K> keys, Function<? super K, ? extends V> mapper)
keys. The returned map is an unmodifiable map whose keys are known at construction. The map's values are computed via the provided mapper when they are first accessed (e.g. via Map::get).
The provided mapper function is guaranteed to be successfully invoked at most once per key, even in a multi-threaded environment. Competing threads accessing a value already under computation will block until an element is computed or an exception is thrown by the computing thread.
If invoking the provided mapper function throws an exception, it is rethrown to the initial caller and no value associated with the provided key is recorded.
Any Map.values() or Map.entrySet() views of the returned map are also stable.
The returned map is unmodifiable and does not implement the optional operations in the Map interface.
If the provided mapper recursively calls the returned map for the same key, an IllegalStateException will be thrown.
K - the type of keys maintained by the returned mapV - the type of mapped values in the returned mapkeys - the (non-null) keys in the returned mapmapper - to invoke whenever an associated value is first accessed (may return null)keys
NullPointerException - if the provided set of inputs contains a null element.
© 1993, 2025, Oracle and/or its affiliates. All rights reserved.
Documentation extracted from Debian's OpenJDK Development Kit package.
Licensed under the GNU General Public License, version 2, with the Classpath Exception.
Various third party code in OpenJDK is licensed under different licenses (see Debian package).
Java and OpenJDK are trademarks or registered trademarks of Oracle and/or its affiliates.
https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/lang/StableValue.html
StableValuewhen preview features are enabled.