ThreadLocal and Scoped Values
Use ThreadLocal for per-thread data and ScopedValue (Java 21+) for structured concurrency contexts.
import java.util.concurrent.*;
public class ThreadLocalDemo {
// ThreadLocal for request context
private static final ThreadLocal<RequestContext> context = new ThreadLocal<>();
record RequestContext(String requestId, String userId, long startTime) {}
// Set at beginning of request processing
static void handleRequest(String requestId, String userId) {
context.set(new RequestContext(requestId, userId, System.currentTimeMillis()));
try {
serviceLayer();
} finally {
context.remove(); // ALWAYS clean up to prevent memory leaks
}
}
static void serviceLayer() {
RequestContext ctx = context.get();
System.out.printf("Service [req=%s, user=%s]%n", ctx.requestId(), ctx.userId());
repositoryLayer();
}
static void repositoryLayer() {
RequestContext ctx = context.get();
long elapsed = System.currentTimeMillis() - ctx.startTime();
System.out.printf("Repository [req=%s, elapsed=%dms]%n", ctx.requestId(), elapsed);
}
// InheritableThreadLocal — passes to child threads
private static final InheritableThreadLocal<String> traceId =
new InheritableThreadLocal<>();
// ThreadLocal counter (per-thread)
private static final ThreadLocal<Integer> counter =
ThreadLocal.withInitial(() -> 0);
static void increment() {
counter.set(counter.get() + 1);
System.out.printf("Thread %s: count=%d%n",
Thread.currentThread().getName(), counter.get());
}
public static void main(String[] args) throws Exception {
// Request context demo
ExecutorService pool = Executors.newFixedThreadPool(2);
pool.submit(() -> handleRequest("req-001", "alice"));
pool.submit(() -> handleRequest("req-002", "bob"));
// Per-thread counter
Thread t1 = new Thread(() -> { increment(); increment(); increment(); }, "T1");
Thread t2 = new Thread(() -> { increment(); increment(); }, "T2");
t1.start(); t2.start();
t1.join(); t2.join();
// T1: 1,2,3 and T2: 1,2 — independent
// InheritableThreadLocal
traceId.set("trace-abc");
new Thread(() -> System.out.println("Child trace: " + traceId.get())).start();
pool.shutdown();
pool.awaitTermination(5, TimeUnit.SECONDS);
}
}Use Cases
- Request-scoped context in web servers
- Per-thread counters and accumulators
- Distributed tracing context propagation
Tags
Related Snippets
Similar patterns you can reuse in the same workflow.
CompletableFuture — Async Programming
Chain async operations with CompletableFuture: thenApply, thenCompose, allOf, and exception handling.
Best for: Parallel API calls to multiple services
Virtual Threads — Lightweight Concurrency
Use Java 21 virtual threads for massive concurrency without thread pool tuning or reactive frameworks.
Best for: High-concurrency web servers handling thousands of requests
ExecutorService — Thread Pool Management
Create and manage thread pools with ExecutorService: fixed, cached, scheduled, and custom pools.
Best for: Managing concurrent task execution
Concurrent Collections — Thread-Safe Maps
Use ConcurrentHashMap, CopyOnWriteArrayList, and BlockingQueue for thread-safe data structures.
Best for: Thread-safe caching in multi-threaded applications