Share
Structured Concurrency, Scoped Values and Joiners in the JDK 25 26 27
Loom already delivered virtual threads and scoped values. It still needs to deliver one last element: the Structured Concurrency API, a preview feature of JDK 26. As of now, launching requets in parallel can be done using the ExecutorService pattern, or callback-based asynchronous programming. The Structured Concurrency API gives you new patterns of code, based on virtual threads, to launch this request in parallel, following the principle of structured imperative programming. The code you write is more readable, with less places for bugs to hide. It also gives you meaningful stack traces for better debugability and profiling. This presentation takes you through these patterns, showing you how the StructuredTaskScope objects are working, and how you can configure them. You will see how you can build your own asynchronous strategies for the handling of the results your tasks give you using Joiners, and how you can build them to exactly fit the specific needs of your application.
José
Transcript
121 slides
-
The Structured Concurrency API in JDK 26 (and 27) Scoped
Values and Joiners José Paumard Java Developer Advocate Java Platform Group -
Tune in! 5 Copyright © 2025, Oracle and/or its affiliates
| Inside Java Newscast JEP Café Road To 25 series Inside.java Inside Java Podcast Sip of Java Cracking the Java coding interview -
OpenJDK is the place where it all happens 6 Copyright
© 2025, Oracle and/or its affiliates | https://openjdk.org/ -
OpenJDK is the place where it all happens 7 Copyright
© 2025, Oracle and/or its affiliates | https://jdk.java.net/ -
First Preview in the JDK 19 9 Copyright © 2025,
Oracle and/or its affiliates | -
Virtual Threads: Final in 21 Pinning on synchronization was solved
in 24 10 Copyright © 2025, Oracle and/or its affiliates | -
Scoped Value: Final in 25 11 Copyright © 2025, Oracle
and/or its affiliates | -
Structured Concurrency: Preview in 26 12 Copyright © 2025, Oracle
and/or its affiliates | -
… and in 27! 13 Copyright © 2025, Oracle and/or
its affiliates | -
What is Loom About? Loom is about fixing concurrency issues
3 issues to fix: - Blocking a platform thread is bad - Having a non-relevant stack trace is annoying - Having loose threads is a waste of resources 14 Copyright © 2025, Oracle and/or its affiliates | -
Virtual Threads A virtual thread is a regular Java object
Blocking a virtual thread does not block its carrier thread Virtual threads should only be used for blocking tasks Don’t use virtual threads for long running in-memory computations! 15 Copyright © 2025, Oracle and/or its affiliates | -
Why GOTO are Banned? 17 Copyright © 2025, Oracle and/or
its affiliates | -
Why GOTO are Banned? Because a GOTO can take you
anywhere in your code When a bug occurs on a given line It’s impossible to know where the program is coming from There is no stack trace when using GOTO 18 Copyright © 2025, Oracle and/or its affiliates | -
Executor Running a Task in a Worker Thread 19 Copyright
© 2025, Oracle and/or its affiliates | Instructions Launch a task in another thread More instructions Runnable -
Why GOTO are Banned? Running a task in another thread
Whether it is in an ExecutorService Or through a CompletableFuture Is like using a GOTO! The information on the caller is lost Structured Concurrency fixes that 20 Copyright © 2025, Oracle and/or its affiliates | -
Executor Running a Task in a Worker Thread 21 Copyright
© 2025, Oracle and/or its affiliates | Instructions Launch a task in another thread More instructions Runnable -
Executor What About ThreadLocal? 22 Copyright © 2025, Oracle and/or
its affiliates | Instructions Launch a task in another thread More instructions Set a ThreadLocal -
Executor What About ThreadLocal? 23 Copyright © 2025, Oracle and/or
its affiliates | Instructions Launch a task in another thread More instructions Set a ThreadLocal -
Executor What About ThreadLocal? 24 Copyright © 2025, Oracle and/or
its affiliates | Instructions Launch a task in another thread More instructions Set a ThreadLocal If not removed… … it is also available here! -
Running Asynchronous Tasks 25 Copyright © 2025, Oracle and/or its
affiliates | Instructions async tasks Executor -
Running Asynchronous Tasks 26 Copyright © 2025, Oracle and/or its
affiliates | Instructions async tasks Executor -
Running Asynchronous Tasks 27 Copyright © 2025, Oracle and/or its
affiliates | Instructions async tasks Executor -
Running Asynchronous Tasks 28 Copyright © 2025, Oracle and/or its
affiliates | Instructions async tasks Executor -
Running Asynchronous Tasks 29 Copyright © 2025, Oracle and/or its
affiliates | Instructions async tasks Executor -
Running Asynchronous Tasks 30 Copyright © 2025, Oracle and/or its
affiliates | StructuredTaskScope Instructions async tasks -
Running Asynchronous Tasks 31 Copyright © 2025, Oracle and/or its
affiliates | StructuredTaskScope Instructions async tasks -
Running Asynchronous Tasks 32 Copyright © 2025, Oracle and/or its
affiliates | More instructions StructuredTaskScope close() Instructions async tasks scope.close() is called It interrupts the threads that are still running -
Running Asynchronous Tasks 33 Copyright © 2025, Oracle and/or its
affiliates | StructuredTaskScope close() Instructions async tasks It a task runs forever, and does not react to the interruption Then close() blocks You can get a stack trace that helps you debug the situation -
Running Asynchronous Tasks 34 Copyright © 2025, Oracle and/or its
affiliates | More instructions StructuredTaskScope close() Instructions async tasks No more loose thread in your application ☺ -
How is Structured Concurrency Working? Structured Concurrency consists in creating
an object 1) That you create when needed 2) That accepts tasks (Runnable, Callable) 3) That executes them in virtual threads 4) That gives you tools to analyze the results, and deal with timeouts and exceptions 5) That is closed after use 35 Copyright © 2025, Oracle and/or its affiliates | -
How is Structured Concurrency Working? Using a StructuredTaskScope: 36 Copyright
© 2025, Oracle and/or its affiliates | try (var scope = StructuredTaskScope.open()) { } -
How is Structured Concurrency Working? Using a StructuredTaskScope: 37 Copyright
© 2025, Oracle and/or its affiliates | try (var scope = StructuredTaskScope.open()) { var imagesSubtask = scope.fork(() -> someService.readImages()); var linksSubtask = scope.fork(() -> someService.readLinks()); } -
How is Structured Concurrency Working? Using a StructuredTaskScope: 38 Copyright
© 2025, Oracle and/or its affiliates | try (var scope = StructuredTaskScope.open()) { var imagesSubtask = scope.fork(() -> someService.readImages()); var linksSubtask = scope.fork(() -> someService.readLinks()); scope.join(); // blocks until all the tasks complete } -
How is Structured Concurrency Working? Using a StructuredTaskScope: 39 Copyright
© 2025, Oracle and/or its affiliates | try (var scope = StructuredTaskScope.open()) { var imagesSubtask = scope.fork(() -> someService.readImages()); var linksSubtask = scope.fork(() -> someService.readLinks()); scope.join(); var page = new Page(imagesSubtask.get(), linksSubtask.get()); // do something with page } -
How is Structured Concurrency Working? Using a StructuredTaskScope: 40 Copyright
© 2025, Oracle and/or its affiliates | try (var scope = StructuredTaskScope.open()) { var imagesSubtask = scope.fork(() -> someService.readImages()); var linksSubtask = scope.fork(() -> someService.readLinks()); scope.join(); var page = new Page(imagesSubtask.get(), linksSubtask.get()); // do something with page } // scope.close() is called -
How is Structured Concurrency Working? The Subtask object UNAVAILABLE is
the default state Moves to the SUCCESS or the FAILED state 42 Copyright © 2025, Oracle and/or its affiliates | sealed interface Subtask<T> { State state(); // UNAVAILABLE | SUCCESS | FAILED T get(); // get() and exception() Throwable exception(); // must be called after join() } -
How is Structured Concurrency Working? What if a callable throws
an exception? FailedException wraps this exception 43 Copyright © 2025, Oracle and/or its affiliates | try (var scope = StructuredTaskScope.open()) { scope.fork(() -> { throw new HorribleException(); }); try { scope.join(); } catch (FailedException e) { ... } // ... } -
How is Structured Concurrency Working? What if a callable throws
an exception? 44 Copyright © 2025, Oracle and/or its affiliates | try (var scope = StructuredTaskScope.open()) { 62: var imagesSubtask = scope.fork(() -> someService.readImages()); var linksSubtask = scope.fork(() -> someService.readLinks()); try { scope.join(); } catch (FailedException _) {} if (imagesSubtask.state() == State.FAILED) { imagesSubtask.exception().printStackTrace(); } // ... } -
How is Structured Concurrency Working? What if something goes wrong?
45 Copyright © 2025, Oracle and/or its affiliates | java.lang.IllegalStateException: Boom! at org.myapp.SomeService.readImages(SomeService.java:12) at org.myapp.MyApp.lambda$main$0(MyApp.java:62) at java.base/java.util.concurrent. StructuredTaskScope$SubtaskImpl.run(StructuredTaskScope.java:893) at java.base/java.lang.VirtualThread.run(VirtualThread.java:329) -
STS Configuration Uses the Ruby builder pattern 46 Copyright ©
2025, Oracle and/or its affiliates | try (var scope = StructuredTaskScope.open( joiner, // more on that in a minute configuration -> configuration.withName(...) .withThreadFactory(...) .withTimeout(...))) { // ... } -
STS Default Strategy Default behavior: join() throws an exception if
a task fails, and returns null 47 Copyright © 2025, Oracle and/or its affiliates | try (var scope = StructuredTaskScope.open()) { // fork your tasks var result = scope.join(); // ... } -
Choosing a STS Strategy You can also pass a Joiner
48 Copyright © 2025, Oracle and/or its affiliates | Joiner<T, R> joiner = ...; Callable<T> task = ...; try (var scope = StructuredTaskScope.open(joiner)) { // fork your tasks scope.fork(task); R result = scope.join(); // ... } -
Choosing a STS Strategy You can also pass a Joiner
(JDK 27) 49 Copyright © 2025, Oracle and/or its affiliates | Joiner<T, R, R_X> joiner = ...; Callable<T> task = ...; try (var scope = StructuredTaskScope.open(joiner)) { // fork your tasks scope.fork(task); R result = scope.join(); // ... } -
The Joiner API Joiner is an interface that allows the
definition of a strategy: - How a succeeding or failing task impacts the STS - What the join() returns A Joiner can decide to cancel a STS - On the submission of a task - On the completion (success / failure) of a task 50 Copyright © 2025, Oracle and/or its affiliates | -
The Joiner API The Joiner interface in 25 If onFork()
or onComplete() returns true, the scope is canceled 51 Copyright © 2025, Oracle and/or its affiliates | interface Joiner<T, R> { // T: tasks, R: returned value boolean onFork(Subtask<? extends T> subtask); // called in the main thread boolean onComplete(Subtask<? extends T> subtask); // called in each VT R result() throws Throwable; // returned by join() } -
The Joiner API The Joiner interface in 26 If onFork()
or onComplete() returns true, the scope is canceled 52 Copyright © 2025, Oracle and/or its affiliates | interface Joiner<T, R> { // T: tasks, R: returned value boolean onFork(Subtask<T> subtask); // called in the main thread boolean onComplete(Subtask<T> subtask); // called in each VT R result() throws Throwable; // returned by join() void onTimeout(); } -
The Joiner API in 27 The Joiner interface in 27
If onFork() or onComplete() returns true, the scope is canceled 53 Copyright © 2025, Oracle and/or its affiliates | interface Joiner<T, R, R_X> { // T: tasks, R: returned value, R_X: exception boolean onFork(Subtask<T> subtask); // called in the main thread boolean onComplete(Subtask<T> subtask); // called in each VT R result() throws R_X; // returned / thrown by join() void timeout() throws R_X; } -
Open / Fork / Join / Close 54 Copyright ©
2025, Oracle and/or its affiliates | 1) Create a STS: STS.open(joiner) StructuredTaskScope Main thread -
Open / Fork / Join / Close 55 Copyright ©
2025, Oracle and/or its affiliates | 2) scope.fork(task) For each task: 1) Create a Subtask, state is UNAVAILABLE 2) Calls joiner.onFork(subtask) 3) Creates a VT that runs the task StructuredTaskScope Main thread -
Open / Fork / Join / Close 56 Copyright ©
2025, Oracle and/or its affiliates | Subtask: UNAVAILABLE Subtask: UNAVAILABLE Subtask: UNAVAILABLE Subtask: UNAVAILABLE StructuredTaskScope 3) scope.join() (this is a blocking call) Main thread -
Open / Fork / Join / Close 57 Copyright ©
2025, Oracle and/or its affiliates | StructuredTaskScope 3) scope.join() (this is a blocking call) 1) The subtask state is updated to SUCCESS Subtask: UNAVAILABLE Subtask: UNAVAILABLE Subtask: UNAVAILABLE Subtask: UNAVAILABLE Main thread -
Open / Fork / Join / Close 58 Copyright ©
2025, Oracle and/or its affiliates | StructuredTaskScope 3) scope.join() (this is a blocking call) 1) The subtask state is updated to SUCCESS Subtask: UNAVAILABLE Subtask: SUCCESS Subtask: UNAVAILABLE Subtask: UNAVAILABLE Main thread -
Open / Fork / Join / Close 59 Copyright ©
2025, Oracle and/or its affiliates | StructuredTaskScope 3) scope.join() (this is a blocking call) 1) The subtask state is updated to SUCCESS 2) The result is copied to the subtask 3) onComplete(subtask) is called 4) The VT dies Subtask: UNAVAILABLE Subtask: SUCCESS Subtask: UNAVAILABLE Subtask: UNAVAILABLE Main thread -
Open / Fork / Join / Close 60 Copyright ©
2025, Oracle and/or its affiliates | StructuredTaskScope 3) scope.join() (this is a blocking call) Subtask: UNAVAILABLE Subtask: SUCCESS Subtask: UNAVAILABLE Subtask: UNAVAILABLE Main thread -
Open / Fork / Join / Close 61 Copyright ©
2025, Oracle and/or its affiliates | StructuredTaskScope 3) scope.join() (this is a blocking call) Subtask: UNAVAILABLE Subtask: SUCCESS Subtask: UNAVAILABLE Subtask: UNAVAILABLE Main thread -
Open / Fork / Join / Close 62 Copyright ©
2025, Oracle and/or its affiliates | StructuredTaskScope 3) scope.join() (this is a blocking call) 1) The subtask state is updated to FAILED Subtask: FAILED Subtask: SUCCESS Subtask: UNAVAILABLE Subtask: UNAVAILABLE Main thread -
Open / Fork / Join / Close 63 Copyright ©
2025, Oracle and/or its affiliates | StructuredTaskScope 3) scope.join() (this is a blocking call) 1) The subtask state is updated to FAILED 2) The exception is copied to the subtask 3) onComplete(subtask) is called 4) The VT dies Subtask: FAILED Subtask: SUCCESS Subtask: UNAVAILABLE Subtask: UNAVAILABLE Main thread -
Open / Fork / Join / Close 64 Copyright ©
2025, Oracle and/or its affiliates | StructuredTaskScope 3) scope.join() (this is a blocking call) Subtask: FAILED Subtask: SUCCESS Subtask: UNAVAILABLE Subtask: UNAVAILABLE Main thread -
Open / Fork / Join / Close 65 Copyright ©
2025, Oracle and/or its affiliates | StructuredTaskScope 3) scope.join() (this is a blocking call) Subtask: FAILED Subtask: SUCCESS Subtask: UNAVAILABLE Subtask: SUCCESS Main thread -
Open / Fork / Join / Close 66 Copyright ©
2025, Oracle and/or its affiliates | StructuredTaskScope 3) scope.join() (this is a blocking call) Subtask: FAILED Subtask: SUCCESS Subtask: SUCCESS Subtask: SUCCESS Main thread 1) Calls joiner.result() 2) join() returns the result -
Open / Fork / Join / Close 67 Copyright ©
2025, Oracle and/or its affiliates | StructuredTaskScope 4) scope.join() returns the result Subtask: FAILED Subtask: SUCCESS Subtask: SUCCESS Subtask: SUCCESS Main thread -
Open / Fork / Join / Close 68 Copyright ©
2025, Oracle and/or its affiliates | 5) Exits the try block, scope.close() is called Main thread -
Cancelling a STS onFork() and onComplete() can return true In
that case the scope is immediately canceled 69 Copyright © 2025, Oracle and/or its affiliates | -
Cancelling a STS from onFork() 70 Copyright © 2025, Oracle
and/or its affiliates | 2) scope.fork(task) For the next task onFork() returns true StructuredTaskScope Main thread Subtask: UNAVAILABLE Subtask: UNAVAILABLE It cancels the STS -
Cancelling a STS from onComplete() 71 Copyright © 2025, Oracle
and/or its affiliates | StructuredTaskScope 3) scope.join() (this is a blocking call) onComplete() returns true Subtask: FAILED Subtask: SUCCESS Subtask: SUCCESS Subtask: UNAVAILABLE Main thread It also cancels the STS -
Cancelling a STS When a STS is cancelled: 1) All
the virtual threads are interrupted 2) onComplete() is not called anymore 3) joiner.result() is called 4) sts.join() returns 72 Copyright © 2025, Oracle and/or its affiliates | -
Cancelling a STS Warning: some tasks may complete before their
thread is interrupted So you may have subtasks in SUCCESS or FAILED but onComplete() was not called for them 73 Copyright © 2025, Oracle and/or its affiliates | -
Closing a StructuredTaskScope The call to close() waits for all
the threads to be done If some task does not stop when its virtual thread is interrupted, then close() does not return At least you have a stack trace that takes you where it happens! 74 Copyright © 2025, Oracle and/or its affiliates | -
Dealing with Timeouts 75 Copyright © 2025, Oracle and/or its
affiliates | StructuredTaskScope 3) scope.join() (this is a blocking call) Subtask: UNAVAILABLE Subtask: SUCCESS Subtask: UNAVAILABLE Subtask: FAILED Main thread -
Dealing with Timeouts 76 Copyright © 2025, Oracle and/or its
affiliates | StructuredTaskScope 3) scope.join() (this is a blocking call) Subtask: UNAVAILABLE Subtask: SUCCESS Subtask: UNAVAILABLE Subtask: FAILED Main thread Time is up! -
Dealing with Timeouts 77 Copyright © 2025, Oracle and/or its
affiliates | StructuredTaskScope 3) scope.join() (this is a blocking call) Subtask: UNAVAILABLE Subtask: SUCCESS Subtask: UNAVAILABLE Subtask: FAILED Main thread The default behavior is: 1) The STS is cancelled 3) It interrupts the VTs 4) joiner.onTimeout() is called, throws a TimeoutException 5) You can catch it and analyze the subtasks -
Dealing with Timeouts 78 Copyright © 2025, Oracle and/or its
affiliates | StructuredTaskScope 3) scope.join() (this is a blocking call) Subtask: UNAVAILABLE Subtask: SUCCESS Subtask: UNAVAILABLE Subtask: FAILED Main thread You can set a Joiner: 1) The STS is cancelled 2) joiner.onTimeout() is called 3) If you do not to throw the TimeoutException 4) joiner.result() is called 5) scope.join() returns the result -
Dealing with Timeouts 79 Copyright © 2025, Oracle and/or its
affiliates | 5) Exits the try block and closes the STS Main thread -
Dealing with Timeouts Default behavior: a TimeoutException is thrown by
the call to joiner.onTimeout() so result() is not called This TimeoutException is then thrown by the call to sts.join() You can catch the TimeoutException and analyze the subtasks that you have 80 Copyright © 2025, Oracle and/or its affiliates | -
Dealing with Timeouts You can implement your own strategy with
a Joiner If your joiner.onTimeout() method does not throw Then joiner.result() is called, and you can create a result from the subtasks you have 81 Copyright © 2025, Oracle and/or its affiliates | -
Role of a Joiner The Joiner defines the following: -
Should the scope be cancelled or not, and when - How to compute a result - What to do with exceptions and timeout It works with: - The result or the exception of each task - A possible timeout 82 Copyright © 2025, Oracle and/or its affiliates | -
Available Joiners Factory methods from the Joiner interface: 83 Copyright
© 2025, Oracle and/or its affiliates | Joiner<T, T> anySuccessfulOrThrow(); Joiner<T, List<Subtask<T>> allSuccessfulOrThrow(); Joiner<T, Void> awaitAllSuccessfulOrThrow(); Joiner<T, Void> awaitAll(); Joiner<T, List<Subtask<T>>> allUntil(predicate); Predicate<Subtask<T>> predicate = ...; // returns true cancel the scope -
Implementing Your Own Joiner You need to be careful with
race conditions Example: extracting a max 84 Copyright © 2025, Oracle and/or its affiliates | class MaxJoiner<T extends Comparable<? super T>> implements Joiner<T, T> { final List<Subtask<? extends T>> subtasks = new ArrayList<>(); public boolean onFork(Subtask<? extends T> subtask) { this.subtasks.add(subtask); return false; } } -
Implementing Your Own Joiner You need to be careful with
race conditions Example: extracting a max 85 Copyright © 2025, Oracle and/or its affiliates | class MaxJoiner<T extends Comparable<? super T>> implements Joiner<T, T> { final List<Subtask<? extends T>> subtasks = new ArrayList<>(); public Optional<T> result() { return subtasks.stream() .filter(subtask -> subtask.state() == State.SUCCESS) .map(Subtask::get) .max(Comparator.naturalOrder()); } } -
Implementing Your Own Joiner You need to be careful with
race conditions Example: extracting a max 86 Copyright © 2025, Oracle and/or its affiliates | class MaxJoiner<T extends Comparable<? super T>> implements Joiner<T, T> { final List<T> subtasks ; boolean onComplete(Subtask<? extends T> subtask) { if (subtask.state() == State.SUCCESS) { this.subtasks.add(subtask.get()); } return false; } } -
Implementing Your Own Joiner You need to be careful with
race conditions Example: extracting a max 87 Copyright © 2025, Oracle and/or its affiliates | class MaxJoiner<T extends Comparable<? super T>> implements Joiner<T, T> { final List<T> subtasks = new ArrayList<>(); boolean onComplete(Subtask<? extends T> subtask) { // executed in a VT!! if (subtask.state() == State.SUCCESS) { this.subtasks.add(subtask.get()); } return false; } } -
Implementing Your Own Joiner You need to be careful with
race conditions Example: extracting a max 88 Copyright © 2025, Oracle and/or its affiliates | class MaxJoiner<T extends Comparable<? super T>> implements Joiner<T, T> { final Collection<T> subtasks = new ConcurrentLinkedQueue<>(); boolean onComplete(Subtask<? extends T> subtask) { if (subtask.state() == State.SUCCESS) { this.subtasks.add(subtask.get()); } return false; } } -
Implementing Your Own Joiner You need to be careful with
race conditions Example: extracting a max Think twice before doing anything in onComplete() 89 Copyright © 2025, Oracle and/or its affiliates | -
Scoped Values The goal of Scoped Values is to fix
the issues ThreadLocal have 91 Copyright © 2025, Oracle and/or its affiliates | -
What is Wrong with ThreadLocal? So many things… - They
are mutable - The VM cannot optimize them - They may be kept alive forever Virtual Threads support ThreadLocal variables! 92 Copyright © 2025, Oracle and/or its affiliates | -
Welcome to ScopedValue! ScopedValues are non-modifiable They are not bound
to a particular thread They are bound to a single method call 93 Copyright © 2025, Oracle and/or its affiliates | static final ScopedValue<String> KEY = ScopedValue.newInstance(); -
Welcome to ScopedValue! ScopedValues are non-modifiable They are not bound
to a particular thread They are bound to a single method call 94 Copyright © 2025, Oracle and/or its affiliates | static final ScopedValue<String> KEY = ScopedValue.newInstance(); ... ScopedValue.where(KEY, "Value_1") .run(() -> doSomethingSmart())); -
Welcome to ScopedValue! ScopedValues are non-modifiable They are not bound
to a particular thread They are bound to a single method call 95 Copyright © 2025, Oracle and/or its affiliates | static final ScopedValue<String> KEY = ScopedValue.newInstance(); ... ScopedValue.where(KEY, "Value_1") .where(OTHER_KEY, "Value_2") .run(() -> doSomethingSmart())); -
Welcome to ScopedValue! ScopedValues are non-modifiable They are not bound
to a particular thread They are bound to a single method call 96 Copyright © 2025, Oracle and/or its affiliates | void doSomethingSmart() { if (KEY.isBound()) { String value = KEY.get(); ... } else { throw new IllegalStateException("Key is not bound"); } } -
How Do Scoped Values Work? Each stack has a reference
to the thread running it 97 Copyright © 2025, Oracle and/or its affiliates | Stack main() -
How Do Scoped Values Work? Each thread has a reference
to an empty Snapshot A Snapshot can store bindings between keys and values 98 Copyright © 2025, Oracle and/or its affiliates | Stack main() Snapshot -
How Do Scoped Values Work? The new bindings are stored
in a new snapshot Snapshots form a linked list 99 Copyright © 2025, Oracle and/or its affiliates | void main() { ScopedValue .where(K1, "V1") .where(K2, "V2") .run(() -> readPage())); } Snapshot K1 V1 K2 V2 Stack main() Snapshot -
How Do Scoped Values Work? When you read a binding
You explore the linked list But reading a linked list is slow → you need a cache 100 Copyright © 2025, Oracle and/or its affiliates | void readPage() { var v1 = K1.get(); } Snapshot K1 V1 K2 V2 Stack main() Snapshot readpage() -
How Do Scoped Values Work? When you read a binding
You explore the linked list But reading a linked list is slow -> you need a cache 101 Copyright © 2025, Oracle and/or its affiliates | void readPage() { var v1 = K1.get(); } Snapshot K1 V1 K2 V2 Stack main() Snapshot readpage() K1 -
How Do Scoped Values Work? When a binding is read
1) You check the cache 2) If it is not found then you explore the linked list 3) You populate the cache 102 Copyright © 2025, Oracle and/or its affiliates | void readPage() { var v1 = K1.get(); } Snapshot K1 V1 K2 V2 Stack main() Snapshot readpage() K1 -
Snapshot K1 V1 K2 V2 How Do Scoped Values Work?
103 Copyright © 2025, Oracle and/or its affiliates | void readPage() { var v1 = K1.get(); var v2 = K2.get(); } Stack main() Snapshot readpage() K2 K1 -
Snapshot K1 V1 K2 V2 How Do Scoped Values Work?
All the subsequent reads for K1 and K2 can get the value from the cache 104 Copyright © 2025, Oracle and/or its affiliates | void readPage() { var v1 = K1.get(); var v2 = K2.get(); } Stack main() Snapshot readpage() K2 K1 -
How Do Scoped Values Work? When you create another binding
You remove the keys from the cache 105 Copyright © 2025, Oracle and/or its affiliates | Stack main() void readPage() { var v1 = K1.get(); var v2 = K2.get(); ScopedValue .where(K1, "V10") .run(() -> readLinks())); } Snapshot Snapshot K1 V1 K2 V2 readpage() K2 -
How Do Scoped Values Work? When you create another binding
You remove the keys from the cache, and add a Snapshot 106 Copyright © 2025, Oracle and/or its affiliates | Stack main() void readPage() { var v1 = K1.get(); var v2 = K2.get(); ScopedValue .where(K1, "V10") .run(() -> readLinks())); } Snapshot Snapshot K1 V1 K2 V2 readpage() K2 Snapshot K1 V10 -
How Do Scoped Values Work? The binding are ordered from
the youngest to the oldest So a rebinding always takes precedence 107 Copyright © 2025, Oracle and/or its affiliates | Stack main() void readLinks() { var v1 = K1.get(); var v2 = K2.get(); } Snapshot Snapshot K1 V1 K2 V2 readPage() readLinks() Snapshot K1 V10 K2 K1 -
How Do Scoped Values Work? When returning 1) The keys
are removed 108 Copyright © 2025, Oracle and/or its affiliates | void readPage() { var v1 = K1.get(); ScopedValue .where(K1, "V10") .run(() -> readLinks())); IO.println("Done"); } Stack main() Snapshot Snapshot K1 V1 K2 V2 readPage() readLinks() Snapshot K1 V10 K2 -
How Do Scoped Values Work? When returning 1) The keys
are removed 2) The Snapshot is removed 109 Copyright © 2025, Oracle and/or its affiliates | Stack main() void readPage() { var v1 = K1.get(); ScopedValue .where(K1, "V10") .run(() -> readLinks())); IO.println("Done"); } Snapshot Snapshot K1 V1 K2 V2 readPage() K2 -
How Do Scoped Values Work? Subsequent reads can repopulate the
cache 110 Copyright © 2025, Oracle and/or its affiliates | Stack main() void readPage() { var v1 = K1.get(); ScopedValue .where(K1, "V10") .run(() -> readLinks())); IO.println("Done"); var v2 = K1.get(); } Snapshot Snapshot K1 V1 K2 V2 readPage() K2 K1 -
How Do Scoped Values Work? In the end the cache
is empty, and all the Snapshots are removed 111 Copyright © 2025, Oracle and/or its affiliates | Stack main() void main() { ScopedValue .where(K1, "V1") .where(K2, "V2") .run(() -> readPage())); IO.println("Done"); } Snapshot -
ScopedValue If your task launches other tasks in other threads,
are the scoped values visible from these other tasks? - It could be threads created by your task - Or existing threads 112 Copyright © 2025, Oracle and/or its affiliates | -
Executor Are Scoped Values Sent to Executors? 113 Copyright ©
2025, Oracle and/or its affiliates | Instructions Bind a scoped value More instructions Runnable -
Executor Are Scoped Values Sent to Executors? 114 Copyright ©
2025, Oracle and/or its affiliates | Instructions Bind a scoped value More instructions This could escape the scope of your task Runnable -
Executor Are Scoped Values Sent to Executors? 115 Copyright ©
2025, Oracle and/or its affiliates | Instructions Bind a scoped value More instructions This could escape the scope of your task = possible loose binding! Runnable -
Executor Are Scoped Values Sent to Executors? 116 Copyright ©
2025, Oracle and/or its affiliates | Instructions Bind a scoped value More instructions Scoped value bindings are not available here Runnable -
Bind a scoped value Are Scoped Values Sent to VTs
in STS? 117 Copyright © 2025, Oracle and/or its affiliates | More instructions Instructions async tasks StructuredTaskScope No loose binding is possible -
Bind a scoped value Are Scoped Values Sent to VTs
in STS? 118 Copyright © 2025, Oracle and/or its affiliates | More instructions Instructions async tasks StructuredTaskScope No loose binding is possible = Your async tasks can see the Scoped Value bindings -
ScopedValue Scoped Values are not transmitted to threads reason: you
don’t want loose scoped values They are transmitted to StructuredTaskScope reason: you can’t have loose scoped values 119 Copyright © 2025, Oracle and/or its affiliates | -
Loom: Where Are We? Virtual Threads Final in 21, improved
in 24 Scoped Values Final in 25 StructuredConcurrency Preview in 25, 26, 27 120 Copyright © 2025, Oracle and/or its affiliates | -
Devoxx FR 26 Loom Lab Work on this application yourself!
121 Copyright © 2025, Oracle and/or its affiliates |