Overview
Scoped Values are a modern alternative to Thread-Local variables introduced in Java 20 (as a preview feature) and further refined in later versions. They provide a safer and more efficient way to share immutable data within and across threads.
Key Differences from Thread-Local
| Aspect | Thread-Local | Scoped Values |
|---|---|---|
| Mutability | Mutable | Immutable |
| Inheritance | Manual (InheritableThreadLocal) | Automatic |
| Memory | Can cause memory leaks | Automatic cleanup |
| Performance | Slower access | Faster access |
| Scope | Thread lifetime | Bounded lifetime |
Basic Usage
1. Creating and Using Scoped Values
import java.util.concurrent.StructuredTaskScope; import java.util.concurrent.StructuredTaskScope.Subtask; public class ScopedValuesBasic { // 1. Define a ScopedValue private static final ScopedValue<User> CURRENT_USER = ScopedValue.newInstance(); private static final ScopedValue<Locale> USER_LOCALE = ScopedValue.newInstance(); private static final ScopedValue<RequestContext> REQUEST_CONTEXT = ScopedValue.newInstance(); public static void main(String[] args) { // 2. Bind values and run code within that scope User user = new User("john_doe", "John Doe", "en-US"); RequestContext context = new RequestContext("req-123", System.currentTimeMillis()); ScopedValue.where(CURRENT_USER, user) .where(REQUEST_CONTEXT, context) .run(() -> processRequest()); // Outside the scope, ScopedValue is unbound System.out.println("Outside scope: " + (CURRENT_USER.isBound() ? "Bound" : "Unbound")); // Unbound } private static void processRequest() { // 3. Access the bound value within the scope User user = CURRENT_USER.get(); RequestContext context = REQUEST_CONTEXT.get(); System.out.println("Processing request for: " + user.username()); System.out.println("Request ID: " + context.requestId()); // Call methods that also need the context performBusinessOperation(); } private static void performBusinessOperation() { // Scoped values are automatically available in called methods User user = CURRENT_USER.get(); System.out.println("Business operation for: " + user.username()); } // Record definitions record User(String username, String fullName, String locale) {} record RequestContext(String requestId, long timestamp) {} } 2. Nested Scopes and Rebinding
public class ScopedValuesNested { private static final ScopedValue<String> CURRENT_TENANT = ScopedValue.newInstance(); private static final ScopedValue<Integer> DEPTH = ScopedValue.newInstance(); public static void main(String[] args) { // Outer scope ScopedValue.where(CURRENT_TENANT, "acme-corp") .where(DEPTH, 1) .run(() -> { System.out.println("Outer - Tenant: " + CURRENT_TENANT.get()); System.out.println("Outer - Depth: " + DEPTH.get()); // Nested scope - can rebind values ScopedValue.where(CURRENT_TENANT, "another-tenant") .where(DEPTH, 2) .run(() -> { System.out.println("Inner - Tenant: " + CURRENT_TENANT.get()); System.out.println("Inner - Depth: " + DEPTH.get()); }); // Back to outer scope values System.out.println("Back to Outer - Tenant: " + CURRENT_TENANT.get()); System.out.println("Back to Outer - Depth: " + DEPTH.get()); }); } } Practical Examples
Example 1: Web Request Context Management
public class WebRequestScopedValues { // Scoped values for request context private static final ScopedValue<RequestContext> REQUEST = ScopedValue.newInstance(); private static final ScopedValue<UserPrincipal> USER = ScopedValue.newInstance(); private static final ScopedValue<Locale> LOCALE = ScopedValue.newInstance(); private static final ScopedValue<Map<String, String>> TRACING_HEADERS = ScopedValue.newInstance(); public static class RequestContext { private final String requestId; private final String sessionId; private final String userAgent; private final Instant startTime; public RequestContext(String requestId, String sessionId, String userAgent) { this.requestId = requestId; this.sessionId = sessionId; this.userAgent = userAgent; this.startTime = Instant.now(); } // Getters public String getRequestId() { return requestId; } public String getSessionId() { return sessionId; } public String getUserAgent() { return userAgent; } public Instant getStartTime() { return startTime; } } public static class UserPrincipal { private final String userId; private final String username; private final Set<String> roles; public UserPrincipal(String userId, String username, Set<String> roles) { this.userId = userId; this.username = username; this.roles = Set.copyOf(roles); } // Getters public String getUserId() { return userId; } public String getUsername() { return username; } public Set<String> getRoles() { return roles; } public boolean hasRole(String role) { return roles.contains(role); } } // Simulated HTTP request handler public static void handleHttpRequest(HttpServletRequest request, HttpServletResponse response) { // Extract context from request RequestContext context = extractRequestContext(request); UserPrincipal user = authenticateUser(request); Locale locale = extractLocale(request); Map<String, String> tracingHeaders = extractTracingHeaders(request); // Bind scoped values and process request ScopedValue.where(REQUEST, context) .where(USER, user) .where(LOCALE, locale) .where(TRACING_HEADERS, tracingHeaders) .run(() -> processRequest(request, response)); } private static void processRequest(HttpServletRequest request, HttpServletResponse response) { try { // Access scoped values anywhere in the call stack RequestContext context = REQUEST.get(); UserPrincipal user = USER.get(); logRequestStart(context, user); // Route to appropriate handler String path = request.getPathInfo(); if (path.startsWith("/api/users")) { handleUserApi(request, response); } else if (path.startsWith("/api/products")) { handleProductApi(request, response); } else { handleDefault(request, response); } logRequestEnd(context); } catch (Exception e) { handleError(e, response); } } private static void handleUserApi(HttpServletRequest request, HttpServletResponse response) { UserPrincipal currentUser = USER.get(); RequestContext context = REQUEST.get(); // Check permissions if (!currentUser.hasRole("USER_READ")) { throw new SecurityException("Insufficient permissions"); } // Business logic that needs user context UserService userService = new UserService(); userService.getUserProfile(currentUser.getUserId()); response.setStatus(200); } private static void handleProductApi(HttpServletRequest request, HttpServletResponse response) { // Access tracing headers for distributed tracing Map<String, String> tracing = TRACING_HEADERS.get(); String traceId = tracing.get("trace-id"); // Log with trace context System.out.println("Processing product API with trace: " + traceId); ProductService productService = new ProductService(); productService.listProducts(); response.setStatus(200); } // Utility methods private static RequestContext extractRequestContext(HttpServletRequest request) { return new RequestContext( generateRequestId(), request.getSession().getId(), request.getHeader("User-Agent") ); } private static UserPrincipal authenticateUser(HttpServletRequest request) { // Simulated authentication String authHeader = request.getHeader("Authorization"); // ... authentication logic return new UserPrincipal("user-123", "john_doe", Set.of("USER_READ", "PRODUCT_READ")); } private static Locale extractLocale(HttpServletRequest request) { String langHeader = request.getHeader("Accept-Language"); return langHeader != null ? Locale.forLanguageTag(langHeader) : Locale.getDefault(); } private static Map<String, String> extractTracingHeaders(HttpServletRequest request) { Map<String, String> tracing = new HashMap<>(); tracing.put("trace-id", request.getHeader("X-Trace-Id")); tracing.put("span-id", request.getHeader("X-Span-Id")); return Map.copyOf(tracing); } private static void logRequestStart(RequestContext context, UserPrincipal user) { System.out.printf("[%s] Request started for user %s%n", context.getRequestId(), user.getUsername()); } private static void logRequestEnd(RequestContext context) { Duration duration = Duration.between(context.getStartTime(), Instant.now()); System.out.printf("[%s] Request completed in %d ms%n", context.getRequestId(), duration.toMillis()); } private static void handleError(Exception e, HttpServletResponse response) { response.setStatus(500); // Log error with request context RequestContext context = REQUEST.get(); System.err.printf("[%s] Request failed: %s%n", context.getRequestId(), e.getMessage()); } private static String generateRequestId() { return "req-" + System.currentTimeMillis() + "-" + Math.abs(new Random().nextInt()); } // Service classes that use scoped values public static class UserService { public UserProfile getUserProfile(String userId) { // Access scoped values in service layer UserPrincipal currentUser = USER.get(); RequestContext context = REQUEST.get(); // Authorization check if (!currentUser.getUserId().equals(userId) && !currentUser.hasRole("ADMIN")) { throw new SecurityException("Cannot access other user's profile"); } // Business logic with context System.out.printf("[%s] Fetching profile for user %s%n", context.getRequestId(), userId); return new UserProfile(userId, currentUser.getUsername()); } } public static class ProductService { public List<Product> listProducts() { Locale locale = LOCALE.get(); Map<String, String> tracing = TRACING_HEADERS.get(); // Use locale for internationalization System.out.println("Listing products for locale: " + locale); System.out.println("Trace ID: " + tracing.get("trace-id")); return List.of(new Product("prod-1", "Product 1"), new Product("prod-2", "Product 2")); } } record UserProfile(String userId, String username) {} record Product(String id, String name) {} } Example 2: Structured Concurrency with Scoped Values
public class StructuredConcurrencyScopedValues { private static final ScopedValue<Project> CURRENT_PROJECT = ScopedValue.newInstance(); private static final ScopedValue<String> CORRELATION_ID = ScopedValue.newInstance(); public static class Project { private final String id; private final String name; private final Set<String> permissions; public Project(String id, String name, Set<String> permissions) { this.id = id; this.name = name; this.permissions = Set.copyOf(permissions); } // Getters public String getId() { return id; } public String getName() { return name; } public Set<String> getPermissions() { return permissions; } } public static class ProjectService { public ProjectSummary getProjectSummary(String projectId) throws Exception { Project project = loadProject(projectId); String correlationId = generateCorrelationId(); return ScopedValue.where(CURRENT_PROJECT, project) .where(CORRELATION_ID, correlationId) .call(() -> fetchProjectSummary()); } private ProjectSummary fetchProjectSummary() throws Exception { try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { // Launch concurrent subtasks - they inherit scoped values automatically Subtask<List<TeamMember>> membersTask = scope.fork(this::fetchTeamMembers); Subtask<ProjectStatistics> statsTask = scope.fork(this::fetchProjectStatistics); Subtask<List<ProjectTask>> tasksTask = scope.fork(this::fetchRecentTasks); // Wait for all subtasks to complete scope.join(); scope.throwIfFailed(); // Combine results return new ProjectSummary( CURRENT_PROJECT.get(), membersTask.get(), statsTask.get(), tasksTask.get() ); } } private List<TeamMember> fetchTeamMembers() { Project project = CURRENT_PROJECT.get(); String correlationId = CORRELATION_ID.get(); System.out.printf("[%s] Fetching team members for project %s%n", correlationId, project.getId()); // Simulate API call try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } return List.of( new TeamMember("user1", "Alice", "Developer"), new TeamMember("user2", "Bob", "Designer") ); } private ProjectStatistics fetchProjectStatistics() { Project project = CURRENT_PROJECT.get(); String correlationId = CORRELATION_ID.get(); System.out.printf("[%s] Fetching statistics for project %s%n", correlationId, project.getId()); // Simulate database query try { Thread.sleep(150); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } return new ProjectStatistics(85, 42, 1500000); } private List<ProjectTask> fetchRecentTasks() { Project project = CURRENT_PROJECT.get(); String correlationId = CORRELATION_ID.get(); System.out.printf("[%s] Fetching recent tasks for project %s%n", correlationId, project.getId()); // Simulate external service call try { Thread.sleep(200); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } return List.of( new ProjectTask("task1", "Implement feature", "IN_PROGRESS"), new ProjectTask("task2", "Write tests", "TODO") ); } private Project loadProject(String projectId) { // Simulate project loading return new Project(projectId, "Sample Project", Set.of("READ", "WRITE")); } private String generateCorrelationId() { return "corr-" + System.currentTimeMillis(); } } // Record definitions record ProjectSummary(Project project, List<TeamMember> teamMembers, ProjectStatistics statistics, List<ProjectTask> recentTasks) {} record TeamMember(String id, String name, String role) {} record ProjectStatistics(int completionPercentage, int openIssues, long budget) {} record ProjectTask(String id, String description, String status) {} public static void main(String[] args) throws Exception { ProjectService service = new ProjectService(); ProjectSummary summary = service.getProjectSummary("proj-123"); System.out.println("Project: " + summary.project().getName()); System.out.println("Team members: " + summary.teamMembers().size()); System.out.println("Completion: " + summary.statistics().completionPercentage() + "%"); } } Example 3: Database Transaction Management
public class TransactionScopedValues { private static final ScopedValue<Connection> DB_CONNECTION = ScopedValue.newInstance(); private static final ScopedValue<TransactionContext> TX_CONTEXT = ScopedValue.newInstance(); private static final ScopedValue<User> CURRENT_USER = ScopedValue.newInstance(); public static class TransactionContext { private final String transactionId; private final IsolationLevel isolationLevel; private final boolean readOnly; private final Instant startTime; public TransactionContext(String transactionId, IsolationLevel isolationLevel, boolean readOnly) { this.transactionId = transactionId; this.isolationLevel = isolationLevel; this.readOnly = readOnly; this.startTime = Instant.now(); } // Getters public String getTransactionId() { return transactionId; } public IsolationLevel getIsolationLevel() { return isolationLevel; } public boolean isReadOnly() { return readOnly; } public Instant getStartTime() { return startTime; } } public enum IsolationLevel { READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE } public static class TransactionalService { public <T> T executeInTransaction(TransactionCallback<T> callback) { return executeInTransaction(IsolationLevel.READ_COMMITTED, false, callback); } public <T> T executeInTransaction(IsolationLevel isolationLevel, boolean readOnly, TransactionCallback<T> callback) { String txId = generateTransactionId(); TransactionContext txContext = new TransactionContext(txId, isolationLevel, readOnly); try (Connection connection = DataSource.getConnection()) { // Set up transaction connection.setAutoCommit(false); connection.setTransactionIsolation(isolationLevel.ordinal() + 1); return ScopedValue.where(DB_CONNECTION, connection) .where(TX_CONTEXT, txContext) .where(CURRENT_USER, getCurrentUser()) .call(() -> { try { T result = callback.doInTransaction(); connection.commit(); logTransactionSuccess(txContext); return result; } catch (Exception e) { connection.rollback(); logTransactionFailure(txContext, e); throw new TransactionException("Transaction failed", e); } }); } catch (SQLException e) { throw new TransactionException("Failed to manage transaction", e); } } public void updateUserProfile(String userId, UserProfile updates) { executeInTransaction(() -> { // All database operations in this scope use the same connection UserDAO userDao = new UserDAO(); AuditDAO auditDao = new AuditDAO(); User user = userDao.findById(userId); if (user == null) { throw new IllegalArgumentException("User not found: " + userId); } // Update user userDao.update(userId, updates); // Audit the change auditDao.logUserUpdate(userId, updates, CURRENT_USER.get()); return null; }); } public Order processOrder(OrderRequest request) { return executeInTransaction(IsolationLevel.REPEATABLE_READ, false, () -> { OrderDAO orderDao = new OrderDAO(); InventoryDAO inventoryDao = new InventoryDAO(); PaymentDAO paymentDao = new PaymentDAO(); // Check inventory if (!inventoryDao.hasSufficientStock(request.productId(), request.quantity())) { throw new InsufficientStockException("Not enough stock"); } // Process payment PaymentResult payment = paymentDao.processPayment( request.paymentMethod(), request.amount()); if (!payment.isSuccess()) { throw new PaymentException("Payment failed: " + payment.getErrorMessage()); } // Create order Order order = orderDao.createOrder(request, CURRENT_USER.get()); // Update inventory inventoryDao.reduceStock(request.productId(), request.quantity()); // Log audit trail auditOrderCreation(order); return order; }); } private void auditOrderCreation(Order order) { TransactionContext txContext = TX_CONTEXT.get(); User currentUser = CURRENT_USER.get(); AuditDAO auditDao = new AuditDAO(); auditDao.logOrderCreation(order, currentUser, txContext.getTransactionId()); } private String generateTransactionId() { return "tx-" + System.currentTimeMillis() + "-" + Math.abs(new Random().nextInt(1000)); } private User getCurrentUser() { // In real application, get from security context return new User("current-user", "System User"); } private void logTransactionSuccess(TransactionContext context) { Duration duration = Duration.between(context.getStartTime(), Instant.now()); System.out.printf("[%s] Transaction completed successfully in %d ms%n", context.getTransactionId(), duration.toMillis()); } private void logTransactionFailure(TransactionContext context, Exception e) { System.err.printf("[%s] Transaction failed: %s%n", context.getTransactionId(), e.getMessage()); } } // DAO classes that use scoped connection public static class UserDAO { public User findById(String userId) { Connection connection = DB_CONNECTION.get(); TransactionContext txContext = TX_CONTEXT.get(); try (PreparedStatement stmt = connection.prepareStatement( "SELECT * FROM users WHERE id = ?")) { stmt.setString(1, userId); ResultSet rs = stmt.executeQuery(); if (rs.next()) { return new User(rs.getString("id"), rs.getString("name")); } return null; } catch (SQLException e) { throw new DataAccessException("Failed to find user", e); } } public void update(String userId, UserProfile updates) { Connection connection = DB_CONNECTION.get(); try (PreparedStatement stmt = connection.prepareStatement( "UPDATE users SET name = ?, email = ? WHERE id = ?")) { stmt.setString(1, updates.name()); stmt.setString(2, updates.email()); stmt.setString(3, userId); stmt.executeUpdate(); } catch (SQLException e) { throw new DataAccessException("Failed to update user", e); } } } public static class AuditDAO { public void logUserUpdate(String userId, UserProfile updates, User currentUser) { Connection connection = DB_CONNECTION.get(); TransactionContext txContext = TX_CONTEXT.get(); try (PreparedStatement stmt = connection.prepareStatement( "INSERT INTO audit_log (action, user_id, target_user, transaction_id) VALUES (?, ?, ?, ?)")) { stmt.setString(1, "USER_UPDATE"); stmt.setString(2, currentUser.id()); stmt.setString(3, userId); stmt.setString(4, txContext.getTransactionId()); stmt.executeUpdate(); } catch (SQLException e) { throw new DataAccessException("Failed to log audit", e); } } public void logOrderCreation(Order order, User currentUser, String transactionId) { // Similar implementation... } } // Other DAO classes (simplified) public static class OrderDAO { public Order createOrder(OrderRequest request, User user) { // Implementation... return new Order("order-123", request.productId(), request.quantity()); } } public static class InventoryDAO { public boolean hasSufficientStock(String productId, int quantity) { // Implementation... return true; } public void reduceStock(String productId, int quantity) { // Implementation... } } public static class PaymentDAO { public PaymentResult processPayment(String paymentMethod, double amount) { // Implementation... return new PaymentResult(true, null); } } // Functional interface for transaction callbacks @FunctionalInterface public interface TransactionCallback<T> { T doInTransaction(); } // Record definitions record User(String id, String name) {} record UserProfile(String name, String email) {} record OrderRequest(String productId, int quantity, String paymentMethod, double amount) {} record Order(String id, String productId, int quantity) {} record PaymentResult(boolean success, String errorMessage) {} // Exception classes public static class TransactionException extends RuntimeException { public TransactionException(String message) { super(message); } public TransactionException(String message, Throwable cause) { super(message, cause); } } public static class DataAccessException extends RuntimeException { public DataAccessException(String message, Throwable cause) { super(message, cause); } } public static class InsufficientStockException extends RuntimeException { public InsufficientStockException(String message) { super(message); } } public static class PaymentException extends RuntimeException { public PaymentException(String message) { super(message); } } // Mock DataSource public static class DataSource { public static Connection getConnection() throws SQLException { // In real application, return from connection pool return null; // Simplified } } } Advanced Patterns
1. Scoped Value with Fallback
public class ScopedValueWithFallback { private static final ScopedValue<Configuration> CONFIG = ScopedValue.newInstance(); private static final Configuration DEFAULT_CONFIG = new Configuration("default", "info"); public static Configuration getCurrentConfig() { return CONFIG.orElse(DEFAULT_CONFIG); } public static void withConfig(Configuration config, Runnable task) { ScopedValue.where(CONFIG, config).run(task); } public static <T> T withConfig(Configuration config, Supplier<T> task) { return ScopedValue.where(CONFIG, config).call(task); } // Usage public static void main(String[] args) { // Use default config System.out.println("Default config: " + getCurrentConfig()); // Use scoped config Configuration customConfig = new Configuration("custom", "debug"); withConfig(customConfig, () -> { System.out.println("Scoped config: " + getCurrentConfig()); // Nested with different config withConfig(new Configuration("nested", "trace"), () -> { System.out.println("Nested config: " + getCurrentConfig()); }); // Back to outer scoped config System.out.println("Back to scoped config: " + getCurrentConfig()); }); // Back to default System.out.println("Back to default: " + getCurrentConfig()); } record Configuration(String name, String logLevel) {} } 2. Scoped Value Validation
public class ScopedValueValidation { private static final ScopedValue<UserSession> SESSION = ScopedValue.newInstance(); public static void requireAuthenticated() { if (!SESSION.isBound()) { throw new IllegalStateException("No user session bound"); } UserSession session = SESSION.get(); if (!session.isValid()) { throw new SecurityException("User session is invalid"); } } public static void requirePermission(String permission) { requireAuthenticated(); UserSession session = SESSION.get(); if (!session.hasPermission(permission)) { throw new SecurityException("Insufficient permissions: " + permission); } } public static void withSession(UserSession session, Runnable task) { if (!session.isValid()) { throw new IllegalArgumentException("Invalid session"); } ScopedValue.where(SESSION, session).run(task); } public static class UserSession { private final String userId; private final Set<String> permissions; private final Instant expiry; public UserSession(String userId, Set<String> permissions, Duration duration) { this.userId = userId; this.permissions = Set.copyOf(permissions); this.expiry = Instant.now().plus(duration); } public boolean isValid() { return Instant.now().isBefore(expiry); } public boolean hasPermission(String permission) { return permissions.contains(permission); } // Getters public String getUserId() { return userId; } public Set<String> getPermissions() { return permissions; } public Instant getExpiry() { return expiry; } } } Migration from Thread-Local
1. Thread-Local to Scoped Value Migration
public class ThreadLocalMigration { // OLD: Thread-Local approach private static final ThreadLocal<UserContext> THREAD_LOCAL_CONTEXT = new ThreadLocal<>(); public static void oldApproach() { // Setting value THREAD_LOCAL_CONTEXT.set(new UserContext("user1")); try { // Using value UserContext context = THREAD_LOCAL_CONTEXT.get(); processRequest(context); } finally { // Must remember to clean up! THREAD_LOCAL_CONTEXT.remove(); } } // NEW: Scoped Value approach private static final ScopedValue<UserContext> SCOPED_CONTEXT = ScopedValue.newInstance(); public static void newApproach() { // Setting value with automatic cleanup ScopedValue.where(SCOPED_CONTEXT, new UserContext("user1")) .run(() -> { // Using value UserContext context = SCOPED_CONTEXT.get(); processRequest(context); }); // Automatically cleaned up! } // Migrating InheritableThreadLocal private static final InheritableThreadLocal<UserContext> INHERITABLE_CONTEXT = new InheritableThreadLocal<>(); private static final ScopedValue<UserContext> SCOPED_INHERITABLE_CONTEXT = ScopedValue.newInstance(); public static void migratInheritable() { // OLD: Manual inheritance INHERITABLE_CONTEXT.set(new UserContext("parent")); Thread childThread = new Thread(() -> { UserContext inherited = INHERITABLE_CONTEXT.get(); // Use inherited context }); childThread.start(); // NEW: Automatic inheritance with StructuredTaskScope ScopedValue.where(SCOPED_INHERITABLE_CONTEXT, new UserContext("parent")) .run(() -> { try (var scope = new StructuredTaskScope<String>()) { scope.fork(() -> { // Automatically inherits scoped values UserContext inherited = SCOPED_INHERITABLE_CONTEXT.get(); return "done"; }); scope.join(); } }); } record UserContext(String userId) {} private static void processRequest(UserContext context) { // Processing logic } } Best Practices
- Use for Immutable Data: Scoped values work best with immutable objects
- Keep Scope Small: Bind values for the minimal necessary scope
- Prefer over ThreadLocal: Use scoped values for new code
- Combine with Structured Concurrency: Perfect companion for structured task scopes
- Validate Usage: Check if values are bound when required
- Document Dependencies: Clearly document which scoped values your methods expect
Performance Benefits
- Faster access than ThreadLocal
- No memory leaks due to automatic cleanup
- Better optimizations by the JVM
- Automatic inheritance in structured concurrency
Scoped Values provide a modern, safe, and efficient alternative to Thread-Local variables, especially when combined with Java's structured concurrency features.