What are ScopedValues and how do they differ from ThreadL… — Cracked Java
// Concurrency & Multithreading · Virtual Threads & Structured Concurrency (Loom)
SeniorTheory

What are ScopedValues and how do they differ from ThreadLocal?

ScopedValue is an immutable, dynamically-scoped way to share context (current user, request ID, transaction) with a thread and its subtasks. It is the Loom-era replacement for ThreadLocal: instead of a mutable per-thread slot that you set and must remember to clear, you bind a value for the duration of a run call, and it is automatically unbound when that call returns.

Why ThreadLocal is a poor fit for Loom

ThreadLocal has three problems amplified by virtual threads:

  • Mutable and unbounded lifetime — anyone can set it, and if you forget to remove() it leaks, especially on pooled threads.
  • Memory cost at scale — a ThreadLocal value is retained per thread; with millions of virtual threads that adds up fast.
  • Awkward inheritanceInheritableThreadLocal copies values to children eagerly, which is expensive and surprising.

How ScopedValue works

A ScopedValue is bound for a bounded region. Inside the region, get() returns the value; outside it, the value simply does not exist. Immutability means there is no set to forget to undo and no visibility hazard.

static final ScopedValue<User> CURRENT_USER = ScopedValue.newInstance();

void handle(Request req) {
    User user = authenticate(req);
    ScopedValue.where(CURRENT_USER, user)
               .run(() -> processRequest(req));   // bound only inside run()
    // CURRENT_USER is unbound again here
}

void processRequest(Request req) {
    User u = CURRENT_USER.get();   // reads the binding from the dynamic scope
    ...
}

Inheritance to forked subtasks

The key synergy with structured concurrency: scoped values are automatically and cheaply visible to subtasks forked inside the bound region via StructuredTaskScope. No eager copying — children read the parent's binding directly. So a request's context flows to all its fan-out subtasks for free.

Trade-offs

ScopedValue cannot be mutated in place, so code that relied on ThreadLocal.set mid-flight must be restructured around nested scopes. As of Java 21–24 it has been a preview API alongside structured concurrency, so confirm availability for your JDK.

Mark your status