ScopedValue
A value available to code only for the duration of a call — for example, the current user of a request. A replacement for ThreadLocal.
Available since: Java 25
Syntax
public final class ScopedValue<T>Examples
A value is bound with where(...).run(...) and read with get() in any method called inside:
static final ScopedValue<String> USER = ScopedValue.newInstance();
void handle() {
IO.println("Handling request for " + USER.get());
}
void main() {
ScopedValue.where(USER, "Anna").run(this::handle);
IO.println(USER.isBound());
}Output:
Handling request for Anna
falsecall() returns a result, and a nested binding temporarily replaces the value:
static final ScopedValue<String> USER = ScopedValue.newInstance();
String greet() {
return "Hello, " + USER.orElse("guest");
}
void main() {
IO.println(greet());
IO.println(ScopedValue.where(USER, "Oleg").call(this::greet));
ScopedValue.where(USER, "Anna").run(() -> {
IO.println(greet());
ScopedValue.where(USER, "Mia").run(() -> IO.println(greet()));
IO.println(greet());
});
}Output:
Hello, guest
Hello, Oleg
Hello, Anna
Hello, Mia
Hello, AnnaReading outside a binding:
static final ScopedValue<String> USER = ScopedValue.newInstance();
void main() {
USER.get();
}Output:
Exception in thread "main" java.util.NoSuchElementException: ScopedValue not boundNotes
Unlike ThreadLocal, the value cannot be changed — only rebound for a nested call, and it disappears by itself after run() returns. This way data does not leak between requests and needs no manual cleanup. The object is usually declared as a static final field. The value is visible in child threads of StructuredTaskScope (structured concurrency, a preview in Java 25). The API became final in Java 25.
See also
- Thread.currentThread() — Returns the thread that is running the current code.
- Thread.ofVirtual() — Creates a virtual thread — a lightweight thread you can start by the million.
- Executors.newVirtualThreadPerTaskExecutor() — Creates a service that runs every task in its own virtual thread.
- Runnable — A task with no arguments and no result.
In the tutorial
- Scoped values — ScopedValue in Java 25 — passing data deep into calls without parameters, ScopedValue.newInstance, where(...).run(...), get, nested binding and why scoped values replace ThreadLocal with virtual threads.