Scoped values (preview)

Java 21 Course · lesson 9 of 15 · 4 min read

An immutable, inheritable replacement for ThreadLocal designed for a million threads.

Open this lesson in the learning hub

Key points

  • ThreadLocal is 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.