Scoped values go final
The supported replacement for ThreadLocal in a world of millions of threads.
Open this lesson in the learning hubKey points
- JEP 506 finalises scoped values after four previews - no flag needed on 25.
- A scoped value is immutable and bound only for the duration of one call.
- That removes the two ThreadLocal failure modes: forgetting
remove(), and per-thread copies. - Child threads inside the scope share the binding by reference rather than copying it.
- Rebinding is nested and automatic - the outer value returns when the inner block ends.
Example
// Java 25 (JEP 506) - final, no --enable-preview needed:
//
// static final ScopedValue<String> USER = ScopedValue.newInstance();
//
// ScopedValue.where(USER, "ada").run(() -> handle()); // USER.get() == "ada"
// // outside the block USER is unbound - get() throws
//
// The Java 21 equivalent, with the cleanup you must never forget:
public class Main {
static final ThreadLocal<String> USER = new ThreadLocal<>();
static void handle() {
System.out.println("serving " + USER.get());
}
public static void main(String[] args) {
USER.set("ada");
try {
handle();
} finally {
USER.remove(); // forget this on a pooled thread and it leaks to the next task
}
System.out.println("after remove(): " + USER.get());
System.out.println("a scoped value unbinds itself - there is no remove() to forget");
}
}
Scoped values make the cleanup automatic, which is the bug ThreadLocal could never design away.
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 25 Course course, and every lesson in it is listed on the Java 25 Course contents page.