DEV Community

Sadiul Hakim
Sadiul Hakim

Posted on

Java Scoped Values

Core Idea

Scoped Values provide a way to share immutable data within a defined scope and across threads in a structured manner. Think of them as "contextual variables" that are automatically available to all code running within a specific boundary.

The Mental Model

Imagine you're in a building:

  • Scoped Value: Like the lighting in a room
  • Scope: The room itself
  • Value: The specific light setting (brightness, color)
  • Code: People in the room

Once you set the lights in a room (bind the value), everyone in that room can see with those lights (access the value). When you leave the room, you can't see those lights anymore. Different rooms can have different lighting settings.

Key Characteristics

1. Immutability

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

// Once set, it cannot be changed within the scope
ScopedValue.where(USER, "Alice").run(() -> {
    System.out.println(USER.get()); // "Alice"
    // USER.set("Bob"); // NOT ALLOWED - would cause error
});
Enter fullscreen mode Exit fullscreen mode

2. Structured Scoping

ScopedValue.where(USER, "Alice").run(() -> {
    // This is the "scope" where USER is available
    processRequest();
});

// Outside here, USER is not accessible
Enter fullscreen mode Exit fullscreen mode

3. Thread Safety & Inheritance

ScopedValue.where(user, "Hakim").run(() -> {
        try (var scope = new StructuredTaskScope<String>()) {
            // This will properly inherit the scoped value
            scope.fork(() -> {
                System.out.println("Child thread: " + user.get());
                return "done";
            });

            scope.join(); // Wait for the child thread to complete
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });
Enter fullscreen mode Exit fullscreen mode

How It Works: The Plumbing Metaphor

Think of Scoped Values like water pipes in a building:

  • Declaring a Scoped Value: Installing a pipe system
  • Binding a value: Turning on the water with specific pressure/temperature
  • Running a scope: Opening a room where the water flows
  • Accessing the value: Turning on a faucet in that room
// Install the pipe system
final ScopedValue<String> WATER_TEMP = ScopedValue.newInstance();

// Turn on hot water in the bathroom
ScopedValue.where(WATER_TEMP, "hot").run(() -> {
    takeShower(); // Faucets get hot water
});

// Turn on cold water in the kitchen  
ScopedValue.where(WATER_TEMP, "cold").run(() -> {
    fillWaterBottle(); // Faucets get cold water
});
Enter fullscreen mode Exit fullscreen mode

Real-world Analogies

1. Movie Theater Analogy

final ScopedValue<String> MOVIE = ScopedValue.newInstance();

// Theater 1: Action movie
ScopedValue.where(MOVIE, "Action").run(() -> {
    watchMovie(); // Everyone here sees the action movie
});

// Theater 2: Comedy movie  
ScopedValue.where(MOVIE, "Comedy").run(() -> {
    watchMovie(); // Everyone here sees the comedy
});
Enter fullscreen mode Exit fullscreen mode

2. Construction Site Analogy

final ScopedValue<String> SAFETY_LEVEL = ScopedValue.newInstance();

// High-risk area
ScopedValue.where(SAFETY_LEVEL, "extreme").run(() -> {
    work(); // All workers follow extreme safety protocols
});

// Low-risk area
ScopedValue.where(SAFETY_LEVEL, "normal").run(() -> {
    work(); // Normal safety protocols apply
});
Enter fullscreen mode Exit fullscreen mode

The Magic: Automatic Propagation

The most powerful aspect is how values automatically propagate:

final ScopedValue<String> CONTEXT = ScopedValue.newInstance();

ScopedValue.where(CONTEXT, "important").run(() -> {
    method1(); // CONTEXT is available here
});

void method1() {
    method2(); // CONTEXT is available here too
}

void method2() {
    System.out.println(CONTEXT.get()); // "important" - no need to pass parameters!
}

void method3() {
    ScopedValue.where(user, "Hakim").run(() -> {
        try (var scope = new StructuredTaskScope<String>()) {
            // This will properly inherit the scoped value
            scope.fork(() -> {
                System.out.println("Child thread: " + user.get());
                return "done";
            });

            scope.join(); // Wait for the child thread to complete
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });
}
Enter fullscreen mode Exit fullscreen mode

Why This Matters: Solving Real Problems

Before (ThreadLocal problems):

// ThreadLocal - fragile with virtual threads
ThreadLocal<String> userContext = new ThreadLocal<>();

void processRequest() {
    userContext.set("Alice");
    try {
        // Works but doesn't play well with virtual threads
        Thread.newVirtualThread(this::doWork).start();
    } finally {
        userContext.remove(); // Easy to forget, causes memory leaks
    }
}
Enter fullscreen mode Exit fullscreen mode

After (Scoped Values solution):

// ScopedValue - safe and structured
final ScopedValue<String> USER_CONTEXT = ScopedValue.newInstance();

void processRequest() {
    ScopedValue.where(USER_CONTEXT, "Alice").run(() -> {
        ScopedValue.where(user, "Hakim").run(() -> {
        try (var scope = new StructuredTaskScope<String>()) {
            // This will properly inherit the scoped value
            scope.fork(() -> {
                doWork();
                return "done";
            });

            scope.join(); // Wait for the child thread to complete
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });
    });
    // Automatically cleaned up here
}
Enter fullscreen mode Exit fullscreen mode

The Big Picture

Scoped Values solve several problems at once:

  1. Structured context propagation - Values flow naturally through call stacks
  2. Thread safety - Immutable by design, safe for concurrent access
  3. Virtual thread compatibility - Designed for modern Java concurrency
  4. Memory safety - Automatic cleanup prevents memory leaks
  5. Clean code - Eliminates parameter passing for contextual data

They're particularly valuable for:

  • Web request contexts
  • Transaction management
  • User session information
  • Tracing and logging
  • Feature flag context
  • Security context propagation

Top comments (0)