Scoped values (preview)
An immutable, inheritable replacement for ThreadLocal designed for a million threads.
Open this lesson in the learning hubKey points
ThreadLocalis mutable and must be cleaned up, or a pooled thread leaks it to the next task.- It also copies into every child thread, which is unaffordable when threads are free.
- A scoped value is immutable and bound for the duration of one call:
ScopedValue.where(V, x).run(...). - It unbinds automatically at the end of the block - there is no
remove()to forget. - Child threads inside the scope share the binding by reference rather than copying it.
Example
// Preview API in Java 21 - requires --enable-preview.
public class Main {
static final ScopedValue<String> CURRENT_USER = ScopedValue.newInstance();
static void handleRequest() {
// No parameter threading, and nothing to clean up
System.out.println("serving " + CURRENT_USER.get());
}
public static void main(String[] args) {
ScopedValue.where(CURRENT_USER, "ada").run(() -> {
handleRequest();
ScopedValue.where(CURRENT_USER, "bob").run(Main::handleRequest); // nested rebind
handleRequest(); // back to ada
});
// Outside the scope CURRENT_USER is unbound - calling get() throws.
}
}
Scoped values swap ThreadLocal's mutable per-thread slot for an immutable per-call binding.
This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Java 21 Course course, and every lesson in it is listed on the Java 21 Course contents page.