Java Scoped Values

Java Programming Tutorials


Java 25 finalizes Scoped Values, a feature for safely sharing data through a bounded call context without passing the value through every method parameter. Scoped values are designed for one-way transmission of contextual data and are often a better fit than ThreadLocal for request-scoped information.

A scoped value is bound only while a specific operation runs. When that operation completes, the binding automatically disappears.

Create a ScopedValue

private static final ScopedValue<String> USER =
    ScopedValue.newInstance();

The scoped value starts unbound.

Bind a Value for an Operation

ScopedValue.where(USER, "Asha").run(() -> {
    showUser();
});

Code executed inside the dynamic scope can read the bound value.

Read the Current Value

static void showUser() {
    System.out.println(USER.get());
}

Output:

Asha

A scoped binding is limited to the operation supplied to run() or call(). The value becomes unbound again when the operation exits.

Why Scoped Values?

Applications often need to pass context such as a request ID, user identity, security context, or trace information through several layers of method calls.

Adding the same parameter to every intermediate method can create noise even when most of those methods do not use the value.

Scoped values provide an explicit context key that only code with access to that key can read.

ScopedValue vs ThreadLocal

Characteristic ScopedValue ThreadLocal
Lifetime Bound to a controlled dynamic scope Can remain attached to a thread until removed
Mutation Binding is read-only inside a scope Value can be changed with set()
Cleanup Automatic when the scope ends Often requires explicit remove()
Structured concurrency Designed to work with structured child tasks Inheritance can be more expensive and error-prone

Use call() When a Result Is Needed

String greeting =
    ScopedValue.where(USER, "Noah").call(() -> {
        return "Hello " + USER.get();
    });

System.out.println(greeting);

Output:

Hello Noah

Nested Rebinding

A scoped value can be rebound inside a nested dynamic scope.

ScopedValue.where(USER, "Asha").run(() -> {
    System.out.println(USER.get());

    ScopedValue.where(USER, "Mia").run(() -> {
        System.out.println(USER.get());
    });

    System.out.println(USER.get());
});

Output:

Asha
Mia
Asha

When the nested scope ends, the previous binding becomes visible again.

Check Whether a Value Is Bound

if (USER.isBound()) {
    System.out.println(USER.get());
}

You can also use fallback APIs when an unbound value is acceptable.

Scoped Values with Virtual Threads

Scoped values are designed to work efficiently with virtual threads and structured concurrency. A structured child task can inherit contextual bindings in a controlled way.

This makes scoped values suitable for modern server applications where many lightweight tasks carry request-specific metadata.

Security and Encapsulation

The ScopedValue object acts like a capability. Code cannot read a value unless it can access the scoped-value key itself.

For that reason, keys are often declared private static final so only the intended code can access the contextual data.

Common Mistakes

  • Using ScopedValue for mutable shared state: it is intended for one-way contextual data.
  • Expecting a binding outside its scope: the value becomes unbound when the operation finishes.
  • Creating too many unrelated scoped values: grouping related context into one record can be clearer.
  • Using an older Java release: the finalized API is a Java 25 feature.

Request Context Example

A server can bind a request identifier once near the entry point and let deeper logging code read it without changing every method signature.

private static final ScopedValue<String> REQUEST_ID =\n    ScopedValue.newInstance();\n\nvoid handleRequest(String id) {\n    ScopedValue.where(REQUEST_ID, id).run(() -> {\n        service();\n    });\n}

Any method reached within that dynamic scope can read REQUEST_ID if it has access to the key.

Conclusion

Scoped Values give Java 25 a safer, bounded alternative to many ThreadLocal use cases. They provide automatic lifetime management, read-only contextual bindings, and efficient cooperation with virtual threads and structured concurrency.



Found This Page Useful? Share It!
Get the Latest Tutorials and Updates
Join us on Telegram