Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Virtual Threads, Structured Concurrency et Scop...

Sponsored · SiteGround - Reliable hosting with speed, security, and support you can count on.
Avatar for José José
April 20, 2026

Virtual Threads, Structured Concurrency et Scoped Values en Java 25 et au-delà

Le projet Loom veut simplifier le modèle de programmation des systèmes asynchrones. Le moyen est de permettre le blocage du thread qui réalise les entrées / sorties sans dégrader les performances de l'application. Cela repose sur l'utilisation de threads virtuels, que Loom nous a apporté en Java 21. Alors que les threads Java classiques sont des enveloppes sur les threads de l'OS, les threads virtuels sont de simples objets Java, que l'on peut créer à la demande. Ils posaient toutefois des problèmes de performances en 21 lors de l'exécution d'un appel I/O dans un bloc synchronisé, point qui a été résolu en Java 24. Outre les threads virtuels, Java 21 nous a apporté deux fonctionnalités en preview : les Scoped Values, qui visent à remplacer l'ancienne API ThreadLocal, et l'API Structured Concurrency, qui permet d'organiser les tâches asynchrones d'une application. L'API Scope Value est finale en Java 25. L'API Structured Concurrency quant à elle a été remaniée en 25 et est toujours en preview en 26. Nous présenterons ces trois éléments en détails, avec des exemples et des patterns de code, et aborderons ce que l'on sait des évolutions futures du projet Loom.

Avatar for José

José

April 20, 2026

More Decks by José

Other Decks in Programming

Transcript

  1. Loom in the JDK 25-26-27 Virtual Threads, Structured Concurrency, and

    Scoped Values José Paumard Java Developer Advocate Java Platform Group Rémi Forax Maître de conferences Université Gustave Eiffel
  2. https://twitter.com/Nope! https://github.com/forax https://speakerdeck.com/forax OpenJDK, ASM, Tatoo, Pro, etc… One of

    the Father of invokedynamic (Java 7) Lambda (Java 8), Module (Java 9) Constant dynamic (Java 11) Record, text blocks, sealed types (Java 14 / 15) Valhalla (Java 28+)
  3. Tune in! 8 Copyright © 2026, 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
  4. OpenJDK is the place where it all happens 9 Copyright

    © 2026, Oracle and/or its affiliates https://openjdk.org/
  5. OpenJDK is the place where it all happens 10 Copyright

    © 2026, Oracle and/or its affiliates https://jdk.java.net/
  6. Virtual Threads: Final in 21 With one issue left to

    solve: pinning on synchronization 14 Copyright © 2026, Oracle and/or its affiliates
  7. What is Loom About? Loom is about fixing concurrency issues

    18 Copyright © 2026, Oracle and/or its affiliates
  8. What is Loom About? Why do you need concurrency? For

    a given latency, the throughput of an application is limited by the number of threads The number of threads is limited by the hardware to a few thousands But the hardware can serve much more requests than that! 19 Copyright © 2026, Oracle and/or its affiliates
  9. What is Loom About? The first solution is to split

    each request into atomic, non-blockings tasks And to send them to a reactive framework that will call them when needed But the reactive approach leads to a code that is hard to write and to read, and that is not observable 20 Copyright © 2026, Oracle and/or its affiliates
  10. What is Loom About? A second solution is to increase

    the number of threads, which is not possible with platform threads Virtual threads have been created to be light enough so that you can increase your application throughput for a given latency 21 Copyright © 2026, Oracle and/or its affiliates
  11. A Simple Example Reading images and links Too unefficient to

    be used 24 Copyright © 2026, Oracle and/or its affiliates var image = someService.readImage(); var links = someService.readLinks(); var page = new Page(image, links);
  12. Concurrency Issues The Java 5 (2004) solution (with lambdas and

    var) What is wrong with this code? 25 Copyright © 2026, Oracle and/or its affiliates ExecutorService es = ...; var f1 = es.submit(someService::readImages); var f2 = es.submit(someService::readLinks); var page = new Page(f1.get(1, TimeUnit.SECONDS), f2.get(1, TimeUnit.SECONDS));
  13. Concurrency Issues 1) It blocks threads Blocking a platform thread

    is wrong, for many reasons (Here it is blocked for ~100ms) 26 Copyright © 2026, Oracle and/or its affiliates -> worker thread is blocked -> worker thread is blocked -> the main thread is blocked ExecutorService es = ...; var f1 = es.submit(someService::readImages); var f2 = es.submit(someService::readLinks); var page = new Page(f1.get(1, TimeUnit.SECONDS), f2.get(1, TimeUnit.SECONDS));
  14. Concurrency Issues 2) It can lead to a loose thread

    27 Copyright © 2026, Oracle and/or its affiliates ExecutorService es = ...; var f1 = es.submit(someService::readImages); var f2 = es.submit(someService::readLinks); var page = new Page(f1.get(1, TimeUnit.SECONDS), f2.get(1, TimeUnit.SECONDS));
  15. Concurrency Issues 2) It can lead to a loose thread

    28 Copyright © 2026, Oracle and/or its affiliates ExecutorService es = ...; var f1 = es.submit(someService::readImages); var f2 = es.submit(someService::readLinks); var page = new Page(f1.get(1, TimeUnit.SECONDS), f2.get(1, TimeUnit.SECONDS)); -> if f1.get() crashes...
  16. Concurrency Issues 2) It can lead to a loose thread

    If something goes wrong in readLinks, then the thread running it is never freed. You now have a loose thread! 29 Copyright © 2026, Oracle and/or its affiliates ExecutorService es = ...; var f1 = es.submit(someService::readImages); var f2 = es.submit(someService::readLinks); var page = new Page(f1.get(1, TimeUnit.SECONDS), f2.get(1, TimeUnit.SECONDS)); -> if f1.get() crashes... ... f2.get() is never called
  17. Concurrency Issues 3) It’s hard to debug Stack traces do

    not give you the information you need. 30 Copyright © 2026, Oracle and/or its affiliates ExecutorService es = ...; var f1 = es.submit(someService::readImages); var f2 = es.submit(someService::readLinks); var page = new Page(f1.get(1, TimeUnit.SECONDS), f2.get(1, TimeUnit.SECONDS));
  18. Concurrency Issues 3 issues to fix: - Blocking a platform

    thread is bad - Having loose threads is a waste of resources - Having a non-relevant stack trace is annoying 31 Copyright © 2026, Oracle and/or its affiliates
  19. What is a Platform Thread? A java.lang.Thread is a wrapper

    on a kernel (or platform) thread It needs: - ~1ms to start - ~2MB of memory to store its stack - context switching costs ~0,1ms You can only have several thousands of them 32 Copyright © 2026, Oracle and/or its affiliates
  20. Why is it Wrong to Block a P. Thread? Handling

    one network request per thread Limits your application to ~10k concurrent requests If handling one request consumes 1s of CPU time Then your CPU can handle 1M requests per second The model one request per thread does not work with Platform Threads (anymore) 33 Copyright © 2026, Oracle and/or its affiliates
  21. What Solution? A first solution: - The reactive programming solution:

    have a platform thread handle many requests - How many? ~1k per platform thread 34 Copyright © 2026, Oracle and/or its affiliates
  22. Let’s Rewrite the Previous Code This is the classical way

    of writing it 35 Copyright © 2026, Oracle and/or its affiliates ExecutorService es = ...; var f1 = es.submit(someService::readImages); var f2 = es.submit(someService::readLinks); var page = new Page(f1.get(1, TimeUnit.SECONDS), f2.get(1, TimeUnit.SECONDS));
  23. Let’s Rewrite the Previous Code This is the CompletableFuture way

    of writing it 36 Copyright © 2026, Oracle and/or its affiliates var cf1 = CompletableFuture .supplyAsync(someService::readImages) .whenComplete((images, error) -> { if (error != null) { // log something } }); var cf2 = // same with readLinks var cf3 = cf1.runAfterBoth(cf2, () -> { var page = new Page(cf1.get(), cf2.get()); // do something with page });
  24. How Is Your Platform Thread Used? 37 Copyright © 2026,

    Oracle and/or its affiliates Platform Thread
  25. How Is Your Platform Thread Used? One thing: never write

    blocking code in your lambdas! 38 Copyright © 2026, Oracle and/or its affiliates ~100ns Platform Thread
  26. How Is Your Platform Thread Used? One thing: never write

    blocking code in your lambdas! Blocking a single task for 100ms will block this thread for 1 million tasks  39 Copyright © 2026, Oracle and/or its affiliates ~100ns Platform Thread
  27. What About Debug? Suppose something wrong happens in readImages() Your

    stack trace tells you who called readImages() 40 Copyright © 2026, Oracle and/or its affiliates 27: var images = someService.readImages(); Exception in thread "main" java.lang.IllegalStateException: Boom! at org.myapp.SomeService.readImages(SomeService.java:12) at org.myapp.MyApp.main(MyApp.java:27)
  28. What About Debug? Suppose something wrong happens in readImages() 41

    Copyright © 2026, Oracle and/or its affiliates Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.IllegalStateException: Boom! at java.base/java.util.concurrent.FutureTask.report(FutureTask.java:122) at java.base/java.util.concurrent.FutureTask.get(FutureTask.java:191) at org.myapp.MyApp.main(MyApp.java:32) Caused by: java.lang.IllegalStateException: Boom! at org.myapp.SomeService.readImages(SomeService.java:12) at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317) at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144) at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642) at java.base/java.lang.Thread.run(Thread.java:1575) 29: var f1 = es.submit(someService::readImages); 30: var f2 = es.submit(someService::readLinks); 31: 32: var p = new Page(f1.get(), f2.get());
  29. What About Debug? Suppose something wrong happens in readImages() 42

    Copyright © 2026, Oracle and/or its affiliates var cf1 = CompletableFuture.supplyAsync(SomeService::readImages) .whenComplete((images, error) -> { if (error != null) { error.printStackTrace(); error.getCause().printStackTrace(); } }); var cf2 = // the same with readLinks cf1.runAfterBoth(cf2, () -> { try { var page = new Page(cf1.get(), cf2.get()); // do something with page } catch (Exception e) { // do something with the error } });
  30. What About Debug? Suppose something wrong happens in readImages() 43

    Copyright © 2026, Oracle and/or its affiliates java.util.concurrent.CompletionException: java.lang.IllegalStateException: Boom! at java.base/java.util.concurrent.CompletableFuture.wrapInCompletionException(CompletableFuture.java:323) at java.base/java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:359) at java.base/java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:364) at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1814) at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1804) at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:507) at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1458) at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:2034) at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:189) var cf1 = CompletableFuture.supplyAsync(SomeService::readImages) .whenComplete((images, error) -> { if (error != null) { error.printStackTrace(); error.getCause().printStackTrace(); } });
  31. What About Debug? Suppose something wrong happens in readImages() 44

    Copyright © 2026, Oracle and/or its affiliates var cf1 = CompletableFuture.supplyAsync(SomeService::readImages) .whenComplete((images, error) -> { if (error != null) { error.printStackTrace(); error.getCause().printStackTrace(); } }); java.lang.IllegalStateException: Boom! at org.paumard.SomeService.readImages(SomeService.java:12) at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1812) at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1804) at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:507) at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1458) at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:2034) at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:189)
  32. Async Programming - Efficient - The programming model is hard

    to read / write / test - Impossible to debug - Impossible to profile - Don’t write blocking lambdas! 45 Copyright © 2026, Oracle and/or its affiliates
  33. What is the Second Solution? Two solutions: - The reactive

    programming solution: have a platform thread handle many requests - Build a model of thread lighter than a Platform Thread - How many? ~1k per Platform Thread - How much lighter? At least~1k lighter 46 Copyright © 2026, Oracle and/or its affiliates
  34. Being Light Is Not Enough! You also must: - maintain

    similar performance (same latency) - offer a simple programming model - enable debugging capabilities - support profiling 47 Copyright © 2026, Oracle and/or its affiliates
  35. How Are Virtual Threads Working? Virtual Threads are sent to

    a Fork / Join pool 48 Copyright © 2026, Oracle and/or its affiliates heap Worker 1 fork join pool Worker 2 Worker 3 Worker 4
  36. How Are Virtual Threads Working? Virtual Threads are sent to

    a Fork / Join pool 49 Copyright © 2026, Oracle and/or its affiliates heap Worker 1 fork join pool Worker 2 Worker 3 Worker 4
  37. How Are Virtual Threads Working? … and executed by the

    workers 50 Copyright © 2026, Oracle and/or its affiliates heap Worker 1 fork join pool Worker 2 Worker 3 Worker 4
  38. How Are Virtual Threads Working? … and executed by the

    workers 51 Copyright © 2026, Oracle and/or its affiliates heap Worker 1 fork join pool Worker 2 Worker 3 Worker 4
  39. How Are Virtual Threads Working? … if it issues a

    blocking call 52 Copyright © 2026, Oracle and/or its affiliates heap Worker 1 fork join pool Worker 2 Worker 3 Worker 4
  40. How Are Virtual Threads Working? … if it issues a

    blocking call 53 Copyright © 2026, Oracle and/or its affiliates heap Worker 1 fork join pool Worker 2 Worker 3 Worker 4 blocked!
  41. How Are Virtual Threads Working? … it is unmounted from

    its worker 54 Copyright © 2026, Oracle and/or its affiliates heap Worker 1 fork join pool Worker 2 Worker 3 Worker 4 blocked!
  42. How Are Virtual Threads Working? … and its stack is

    moved to the heap 55 Copyright © 2026, Oracle and/or its affiliates heap Worker 1 fork join pool Worker 2 Worker 3 Worker 4
  43. How Are Virtual Threads Working? … and its stack is

    moved to the heap 56 Copyright © 2026, Oracle and/or its affiliates heap Worker 1 fork join pool Worker 2 Worker 3 Worker 4
  44. How Are Virtual Threads Working? …until the OS signals that

    the data is available 57 Copyright © 2026, Oracle and/or its affiliates heap Worker 1 fork join pool Worker 2 Worker 3 Worker 4
  45. How Are Virtual Threads Working? … at which point it

    is moved back to its wait list 58 Copyright © 2026, Oracle and/or its affiliates heap Worker 1 fork join pool Worker 2 Worker 3 Worker 4
  46. How Are Virtual Threads Working? … at which point it

    is moved back to its wait list 59 Copyright © 2026, Oracle and/or its affiliates heap Worker 1 fork join pool Worker 2 Worker 3 Worker 4
  47. How Are Virtual Threads Working? … where it can be

    stolen from another available worker 60 Copyright © 2026, Oracle and/or its affiliates heap Worker 1 fork join pool Worker 2 Worker 3 Worker 4
  48. How Are Virtual Threads Working? … where it can be

    stolen from another available worker 61 Copyright © 2026, Oracle and/or its affiliates heap Worker 1 fork join pool Worker 2 Worker 3 Worker 4
  49. What Could Prevent Moving a VT Stack? 1) Synchronized blocks:

    fixed in JDK 24 A blocking call from a synchronized block pins the VT 2) Any Java code calling some native code prevents the copy of the stack to the heap. Ex: native calls using FFM, Netty native implementations And since the address is in the C code, it cannot be fixed  62 Copyright © 2026, Oracle and/or its affiliates
  50. Synchronized Before Java 24 Synchronized uses several memory regions as

    lock - If only one thread grabs the lock - The lock is stored on the stack - If another thread tries to grab the lock - The lock is inflated to a fat lock - Allocated / deallocated on the C heap 63 Copyright © 2026, Oracle and/or its affiliates
  51. FastLocking 64 Copyright © 2026, Oracle and/or its affiliates void

    main() { var page = buildPage(); } Page buildPage() { var links = readLinks(); ... return page; } Links readLinks() { synchronized (object) { ... } return links; } Stack buildPage readLinks main
  52. FastLocking Before entering a synchronized block 65 Copyright © 2026,

    Oracle and/or its affiliates Links readLinks() { synchronized (object) { ... } return links; } GC Heap Stack buildPage readLinks main lock bit: 01 field 1 field 2 ...
  53. FastLocking "Allocate" the lock on stack 66 Copyright © 2026,

    Oracle and/or its affiliates Links readLinks() { synchronized (object) { ... } return links; } GC Heap Stack buildPage readLinks main address-00 field 1 field 2 ...
  54. Problem with Virtual Threads There is an inner pointer to

    the stack The stack cannot be copied! 67 Copyright © 2026, Oracle and/or its affiliates Links readLinks() { synchronized (object) { IO.call(); } return links; } GC Heap Stack buildPage readLinks main address-00 field 1 field 2 ...
  55. FastLocking with Liliput Before entering a synchronized block 68 Copyright

    © 2026, Oracle and/or its affiliates Links readLinks() { synchronized (object) { ... } return links; } GC Heap Stack buildPage readLinks main lock bit: 00 field 1 field 2 ... lock stack
  56. FastLocking with Liliput "Allocate" the lock at the bottom of

    the stack 69 Copyright © 2026, Oracle and/or its affiliates Links readLinks() { synchronized (object) { ... } return links; } GC Heap Stack buildPage readLinks main lock bit: 01 field 1 field 2 ... lock stack
  57. No Problem with Virtual Threads There is no inner pointer

    to the stack anymore! 70 Copyright © 2026, Oracle and/or its affiliates Links readLinks() { synchronized (object) { IO.call(); } return links; } GC Heap Stack buildPage readLinks main lock bit: 01 field 1 field 2 ... lock stack
  58. Pinning a VT on a Platform Thread Possible address on

    the stack leads to pinning 71 Copyright © 2026, Oracle and/or its affiliates Links readLinks() { someNativeCall(); } Stack native call Java code int someNativeCall() { int count; int* p = &count; // address on the stack  blockingCall(); }
  59. Pinning a VT on a Platform Thread 72 Copyright ©

    2026, Oracle and/or its affiliates Links readLinks() { someNativeCall(); } Stack int someNativeCall() { int count; int* p = &count; // address on the stack  someJavaUpCall(); } Java code void javaUpCall() { someBlockingCall(); // pinned } native call Java code
  60. Pinning a VT on a Platform Thread Pinning on synchronization:

    fixed in JDK 24 ☺ 73 Copyright © 2026, Oracle and/or its affiliates Links readLinks() { synchronized(key) { blockingCall(); // pinned up to JDK 24 } } Stack Java code
  61. At this Point: 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! 74 Copyright © 2026, Oracle and/or its affiliates
  62. How Is Your Platform Thread Used? So what is the

    difference with the reactive model? 75 Copyright © 2026, Oracle and/or its affiliates Platform Thread
  63. How Is Your Platform Thread Used? Virtual Threads are not

    about performance! They are about programming model and observability 76 Copyright © 2026, Oracle and/or its affiliates Platform Thread
  64. Virtual Threads Programming Model The code you write is the

    most basic code you can write Virtual threads handle the blocking API for you Writing blocking code is OK (Actually, this is what you should do) 77 Copyright © 2026, Oracle and/or its affiliates var images = someService.readImages();
  65. Virtual Threads Programming Model And if something goes wrong: 78

    Copyright © 2026, Oracle and/or its affiliates 78: Callable<Images> fetchImages = () -> someService.readImages(); 79: var future = Executors.newVirtualThreadPerTaskExecutor() 80: .submit(fetchImages); 81: IO.println(future.get()); Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.IllegalStateException: Boom! at java.base/java.util.concurrent.FutureTask.report(FutureTask.java:122) at java.base/java.util.concurrent.FutureTask.get(FutureTask.java:191) at org.paumard.MyApp.main(MyApp.java:81) Caused by: java.lang.IllegalStateException: Boom! at org.paumard.SomeService.readImages(SomeService.java:12) at org.paumard.MyApp.lambda$main$0(MyApp.java:78) at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317) at java.base/java.lang.VirtualThread.run(VirtualThread.java:329)
  66. Scoped Values The goal of Scoped Values is to fix

    the issues ThreadLocal have 81 Copyright © 2026, Oracle and/or its affiliates
  67. 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! 82 Copyright © 2026, Oracle and/or its affiliates
  68. Welcome to ScopedValue! ScopedValues are non-modifiable They are not bound

    to a particular thread They are bound to a single method call 83 Copyright © 2026, Oracle and/or its affiliates static final ScopedValue<String> KEY = ScopedValue.newInstance();
  69. Welcome to ScopedValue! ScopedValues are non-modifiable They are not bound

    to a particular thread They are bound to a single method call 84 Copyright © 2026, Oracle and/or its affiliates static final ScopedValue<String> KEY = ScopedValue.newInstance(); ... ScopedValue.where(KEY, "Value_1") .run(() -> doSomethingSmart()));
  70. Welcome to ScopedValue! ScopedValues are non-modifiable They are not bound

    to a particular thread They are bound to a single method call 85 Copyright © 2026, Oracle and/or its affiliates static final ScopedValue<String> KEY = ScopedValue.newInstance(); ... ScopedValue.where(KEY, "Value_1") .where(OTHER_KEY, "Value_2") .run(() -> doSomethingSmart()));
  71. Welcome to ScopedValue! ScopedValues are non-modifiable They are not bound

    to a particular thread They are bound to a single method call 86 Copyright © 2026, Oracle and/or its affiliates void doSomethingSmart() { if (KEY.isBound()) { String value = KEY.get(); ... } else { throw new IllegalStateException("Key is not bound"); } }
  72. How Do Scoped Values Work? Each stack has a reference

    to the thread running it 87 Copyright © 2026, Oracle and/or its affiliates Stack main()
  73. How Do Scoped Values Work? Each thread has a reference

    to an empty Snapshot A Snapshot can store bindings between keys and values 88 Copyright © 2026, Oracle and/or its affiliates Stack main() Snapshot
  74. How Do Scoped Values Work? The new bindings are stored

    in a new snapshot Snapshots form a linked list 89 Copyright © 2026, Oracle and/or its affiliates void main() { ScopedValue .where(K1, "V1") .where(K2, "V2") .run(() -> readPage())); } Snapshot K1 V1 K2 V2 Stack main() Snapshot
  75. 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 90 Copyright © 2026, Oracle and/or its affiliates void readPage() { var v1 = K1.get(); } Snapshot K1 V1 K2 V2 Stack main() Snapshot readpage()
  76. 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 91 Copyright © 2026, Oracle and/or its affiliates void readPage() { var v1 = K1.get(); } Snapshot K1 V1 K2 V2 Stack main() Snapshot readpage() K1
  77. 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 92 Copyright © 2026, Oracle and/or its affiliates void readPage() { var v1 = K1.get(); } Snapshot K1 V1 K2 V2 Stack main() Snapshot readpage() K1
  78. Snapshot K1 V1 K2 V2 How Do Scoped Values Work?

    93 Copyright © 2026, Oracle and/or its affiliates void readPage() { var v1 = K1.get(); var v2 = K2.get(); } Stack main() Snapshot readpage() K2 K1
  79. 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 94 Copyright © 2026, Oracle and/or its affiliates void readPage() { var v1 = K1.get(); var v2 = K2.get(); } Stack main() Snapshot readpage() K2 K1
  80. How Do Scoped Values Work? When you create another binding

    You remove the keys from the cache 95 Copyright © 2026, 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
  81. How Do Scoped Values Work? When you create another binding

    You remove the keys from the cache, and add a Snapshot 96 Copyright © 2026, 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
  82. How Do Scoped Values Work? The binding are ordered from

    the youngest to the oldest So a rebinding always takes precedence 97 Copyright © 2026, 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
  83. How Do Scoped Values Work? When returning 1) The keys

    are removed 98 Copyright © 2026, 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
  84. How Do Scoped Values Work? When returning 1) The keys

    are removed 2) The Snapshot is removed 99 Copyright © 2026, 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
  85. How Do Scoped Values Work? Subsequent reads can repopulate the

    cache 100 Copyright © 2026, 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
  86. How Do Scoped Values Work? In the end the cache

    is empty, and all the Snapshots are removed 101 Copyright © 2026, Oracle and/or its affiliates Stack main() void main() { ScopedValue .where(K1, "V1") .where(K2, "V2") .run(() -> readPage())); IO.println("Done"); } Snapshot
  87. 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 More on this point later 102 Copyright © 2026, Oracle and/or its affiliates
  88. Why GOTO are Banned? Yes, why? © Apple Corp. 2014

    106 Copyright © 2026, Oracle and/or its affiliates
  89. 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 107 Copyright © 2026, Oracle and/or its affiliates
  90. Executor Running a Task in a Worker Thread 108 Copyright

    © 2026, Oracle and/or its affiliates Instructions Launch a task in another thread More instructions Runnable
  91. 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 109 Copyright © 2026, Oracle and/or its affiliates
  92. Executor Running a Task in a Worker Thread 110 Copyright

    © 2026, Oracle and/or its affiliates Instructions Launch a task in another thread More instructions Runnable
  93. Executor What About ThreadLocal? 111 Copyright © 2026, Oracle and/or

    its affiliates Instructions Launch a task in another thread More instructions Set a ThreadLocal
  94. Executor What About ThreadLocal? 112 Copyright © 2026, Oracle and/or

    its affiliates Instructions Launch a task in another thread More instructions Set a ThreadLocal
  95. Executor What About ThreadLocal? 113 Copyright © 2026, 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!
  96. Running Asynchronous Tasks 114 Copyright © 2026, Oracle and/or its

    affiliates Instructions async tasks Executor
  97. Running Asynchronous Tasks 115 Copyright © 2026, Oracle and/or its

    affiliates Instructions async tasks Executor
  98. Running Asynchronous Tasks 116 Copyright © 2026, Oracle and/or its

    affiliates Instructions async tasks Executor
  99. Running Asynchronous Tasks 117 Copyright © 2026, Oracle and/or its

    affiliates Instructions async tasks Executor
  100. Running Asynchronous Tasks 118 Copyright © 2026, Oracle and/or its

    affiliates Instructions async tasks Executor
  101. Running Asynchronous Tasks 119 Copyright © 2026, Oracle and/or its

    affiliates StructuredTaskScope Instructions async tasks
  102. Running Asynchronous Tasks 120 Copyright © 2026, Oracle and/or its

    affiliates StructuredTaskScope Instructions async tasks
  103. Running Asynchronous Tasks 121 Copyright © 2026, Oracle and/or its

    affiliates More instructions StructuredTaskScope close() Instructions async tasks scope.close() is called It interrupts the threads that are still running
  104. Running Asynchronous Tasks 122 Copyright © 2026, 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
  105. Running Asynchronous Tasks 123 Copyright © 2026, Oracle and/or its

    affiliates More instructions StructuredTaskScope close() Instructions async tasks No more loose thread in your application ☺
  106. Executor Are Scoped Values Sent to Executors? 124 Copyright ©

    2026, Oracle and/or its affiliates Instructions Bind a scoped value More instructions Runnable
  107. Executor Are Scoped Values Sent to Executors? 125 Copyright ©

    2026, Oracle and/or its affiliates Instructions Bind a scoped value More instructions This could escape the scope of your task Runnable
  108. Executor Are Scoped Values Sent to Executors? 126 Copyright ©

    2026, Oracle and/or its affiliates Instructions Bind a scoped value More instructions This could escape the scope of your task = possible loose binding! Runnable
  109. Executor Are Scoped Values Sent to Executors? 127 Copyright ©

    2026, Oracle and/or its affiliates Instructions Bind a scoped value More instructions Scoped value bindings are not available here Runnable
  110. Bind a scoped value Are Scoped Values Sent to VTs

    in STS? 128 Copyright © 2026, Oracle and/or its affiliates More instructions Instructions async tasks StructuredTaskScope No loose binding is possible
  111. Bind a scoped value Are Scoped Values Sent to VTs

    in STS? 129 Copyright © 2026, 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
  112. 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 130 Copyright © 2026, Oracle and/or its affiliates
  113. How is Structured Concurrency Working? Using a StructuredTaskScope: 131 Copyright

    © 2026, Oracle and/or its affiliates try (var scope = StructuredTaskScope.open()) { }
  114. How is Structured Concurrency Working? Using a StructuredTaskScope: 132 Copyright

    © 2026, Oracle and/or its affiliates try (var scope = StructuredTaskScope.open()) { var imagesSubtask = scope.fork(() -> someService.readImages()); var linksSubtask = scope.fork(() -> someService.readLinks()); }
  115. How is Structured Concurrency Working? Using a StructuredTaskScope: 133 Copyright

    © 2026, 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 }
  116. How is Structured Concurrency Working? Using a StructuredTaskScope: 134 Copyright

    © 2026, 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 }
  117. How is Structured Concurrency Working? Using a StructuredTaskScope: 135 Copyright

    © 2026, 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
  118. How is Structured Concurrency Working? The Subtask object UNAVAILABLE is

    the default state Moves to the SUCCESS or the FAILED state 137 Copyright © 2026, 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() }
  119. How is Structured Concurrency Working? What if a callable throws

    an exception? FailedException wraps this exception 138 Copyright © 2026, Oracle and/or its affiliates try (var scope = StructuredTaskScope.open()) { scope.fork(() -> { throw new HorribleException(); }); try { scope.join(); } catch (FailedException e) { ... } // ... }
  120. How is Structured Concurrency Working? What if a callable throws

    an exception? 139 Copyright © 2026, 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(); } // ... }
  121. How is Structured Concurrency Working? What if something goes wrong?

    140 Copyright © 2026, 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)
  122. STS Configuration Uses the Ruby builder pattern 141 Copyright ©

    2026, Oracle and/or its affiliates try (var scope = StructuredTaskScope.open( joiner, // more on that in a minute configuration -> configuration.withName(...) .withThreadFactory(...) .withTimeout(...))) { // ... }
  123. STS Default Strategy Default behavior: join() throws an exception if

    a task fails, and returns null 143 Copyright © 2026, Oracle and/or its affiliates try (var scope = StructuredTaskScope.open()) { // fork your tasks var result = scope.join(); // ... }
  124. Choosing a STS Strategy You can also pass a Joiner

    144 Copyright © 2026, 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(); // ... }
  125. Choosing a STS Strategy You can also pass a Joiner

    (JDK 27) 145 Copyright © 2026, 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(); // ... }
  126. 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 146 Copyright © 2026, Oracle and/or its affiliates
  127. The Joiner API The Joiner interface in 25 If onFork()

    or onComplete() returns true, the scope is canceled 147 Copyright © 2026, 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() }
  128. The Joiner API The Joiner interface in 26 If onFork()

    or onComplete() returns true, the scope is canceled 148 Copyright © 2026, 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(); }
  129. The Joiner API in 27 The Joiner interface in 27

    If onFork() or onComplete() returns true, the scope is canceled 149 Copyright © 2026, 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; }
  130. Open / Fork / Join / Close 150 Copyright ©

    2026, Oracle and/or its affiliates 1) Create a STS: STS.open(joiner) StructuredTaskScope Main thread
  131. Open / Fork / Join / Close 151 Copyright ©

    2026, 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
  132. Open / Fork / Join / Close 152 Copyright ©

    2026, Oracle and/or its affiliates Subtask: UNAVAILABLE Subtask: UNAVAILABLE Subtask: UNAVAILABLE Subtask: UNAVAILABLE StructuredTaskScope 3) scope.join() (this is a blocking call) Main thread
  133. Open / Fork / Join / Close 153 Copyright ©

    2026, 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
  134. Open / Fork / Join / Close 154 Copyright ©

    2026, 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
  135. Open / Fork / Join / Close 155 Copyright ©

    2026, 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
  136. Open / Fork / Join / Close 156 Copyright ©

    2026, Oracle and/or its affiliates StructuredTaskScope 3) scope.join() (this is a blocking call) Subtask: UNAVAILABLE Subtask: SUCCESS Subtask: UNAVAILABLE Subtask: UNAVAILABLE Main thread
  137. Open / Fork / Join / Close 157 Copyright ©

    2026, Oracle and/or its affiliates StructuredTaskScope 3) scope.join() (this is a blocking call) Subtask: UNAVAILABLE Subtask: SUCCESS Subtask: UNAVAILABLE Subtask: UNAVAILABLE Main thread
  138. Open / Fork / Join / Close 158 Copyright ©

    2026, 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
  139. Open / Fork / Join / Close 159 Copyright ©

    2026, 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
  140. Open / Fork / Join / Close 160 Copyright ©

    2026, Oracle and/or its affiliates StructuredTaskScope 3) scope.join() (this is a blocking call) Subtask: FAILED Subtask: SUCCESS Subtask: UNAVAILABLE Subtask: UNAVAILABLE Main thread
  141. Open / Fork / Join / Close 161 Copyright ©

    2026, Oracle and/or its affiliates StructuredTaskScope 3) scope.join() (this is a blocking call) Subtask: FAILED Subtask: SUCCESS Subtask: UNAVAILABLE Subtask: SUCCESS Main thread
  142. Open / Fork / Join / Close 162 Copyright ©

    2026, 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
  143. Open / Fork / Join / Close 163 Copyright ©

    2026, Oracle and/or its affiliates StructuredTaskScope 4) scope.join() returns the result Subtask: FAILED Subtask: SUCCESS Subtask: SUCCESS Subtask: SUCCESS Main thread
  144. Open / Fork / Join / Close 164 Copyright ©

    2026, Oracle and/or its affiliates 5) Exits the try block, scope.close() is called Main thread
  145. Cancelling a STS – 1 165 Copyright © 2026, 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
  146. Cancelling a STS – 2 166 Copyright © 2026, 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
  147. 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 167 Copyright © 2026, Oracle and/or its affiliates
  148. 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 168 Copyright © 2026, Oracle and/or its affiliates
  149. Closing a StructuredTaskScope The call to close() waits for all

    the threads to be done If some task does not stop when interrupted, then close() does not return At least you have a stack trace that takes you where it happens! 169 Copyright © 2026, Oracle and/or its affiliates
  150. Dealing with Timeouts 170 Copyright © 2026, Oracle and/or its

    affiliates StructuredTaskScope 3) scope.join() (this is a blocking call) Subtask: UNAVAILABLE Subtask: SUCCESS Subtask: UNAVAILABLE Subtask: FAILED Main thread
  151. Dealing with Timeouts 171 Copyright © 2026, 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!
  152. Dealing with Timeouts 172 Copyright © 2026, 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
  153. Dealing with Timeouts 173 Copyright © 2026, 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
  154. Dealing with Timeouts 174 Copyright © 2026, Oracle and/or its

    affiliates 5) Exits the try block and closes the STS Main thread
  155. Dealing with Timeouts STS.join() calls Joiner.onTimeout() The default behavior is

    that Joiner.onTimeout() throws a TimeoutException So Joiner.result() is not called, and STS.join() throws this TimeoutException You can catch this exception and analyze the results from the subtasks 175 Copyright © 2026, Oracle and/or its affiliates
  156. 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 STS.join() returns the result returned by Joiner.result() 176 Copyright © 2026, Oracle and/or its affiliates
  157. 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 177 Copyright © 2026, Oracle and/or its affiliates
  158. Available Joiners Cancels the scope if any task fails, join()

    throws an exception If all the tasks are successful: result() returns a stream of all subtasks in the order that they were forked 179 Copyright © 2026, Oracle and/or its affiliates Joiner<T, Stream<Subtask<T>> allSuccessfulOrThrow();
  159. Available Joiners Throws an exception if all tasks fail Returns

    the result of a successful task (no guarantee that it is the first) 180 Copyright © 2026, Oracle and/or its affiliates Joiner<T, T> anySuccessfulOrThrow();
  160. Available Joiners The scope is cancelled if the predicate returns

    true The predicate is invoked in the onComplete() method returns() returns the stream of all subtasks, in fork order The subtasks can be in the SUCCESS, FAILED, or UNAVAILABLE state 181 Copyright © 2026, Oracle and/or its affiliates Predicate<Subtask<T>> predicate = ...; Joiner<T, Stream<Subtask<T>>> joiner = Joiner.allUntil(predicate);
  161. Available Joiners return() returns null when all the subtasks complete

    successfully join() throws an exception if any of the subtask fails Useful when you submit tasks of different types 182 Copyright © 2026, Oracle and/or its affiliates Joiner<T, Void> awaitAllSuccessfulOrThrow();
  162. Available Joiners Returns null Does not throw an exception if

    a task fails Useful for subtask that are doing side effects without producing a result 183 Copyright © 2026, Oracle and/or its affiliates Joiner<T, Void> awaitAll();
  163. Available Joiners 184 Copyright © 2026, Oracle and/or its affiliates

    Wait for all the results Wait on a Predicate Wait for the first result return null return 1 result return all the results awaitSuccessfulOrThrow() awaitAll() anySuccessfulOrThrow() allSuccessfulOrThrow() allUntil(predicate)
  164. Implementing Your Own Joiner You need to be careful with

    race conditions Example: extracting a max 187 Copyright © 2026, Oracle and/or its affiliates class MaxJoiner<T> implements Joiner<T, T> { final Comparator<T> comparator = ...; final List<Subtask<T>> subtasks = new ArrayList<>(); public boolean onFork(Subtask<T> subtask) { this.subtasks.add(subtask); return false; } }
  165. Implementing Your Own Joiner You need to be careful with

    race conditions Example: extracting a max 188 Copyright © 2026, Oracle and/or its affiliates class MaxJoiner<T> implements Joiner<T, T> { final Comparator<T> comparator = ...; final List<Subtask<T>> subtasks = new ArrayList<>(); public T result() { return subtasks.stream() .filter(subtask -> subtask.state() == State.SUCCESS) .map(Subtask::get) .max(comparator).orElseThrow(); } }
  166. Implementing Your Own Joiner You need to be careful with

    race conditions Example: extracting a max 189 Copyright © 2026, Oracle and/or its affiliates class MaxJoiner<T> implements Joiner<T, T> { final Comparator<T> comparator = ...; final List<Subtask<T>> subtasks = new ArrayList<>(); public boolean onComplete(Subtask<T> subtask) { if (subtask.state() == State.SUCCESS) { this.subtasks.add(subtask); } return false; } }
  167. Implementing Your Own Joiner You need to be careful with

    race conditions Example: extracting a max 190 Copyright © 2026, Oracle and/or its affiliates class MaxJoiner<T> implements Joiner<T, T> { final Comparator<T> comparator = ...; final Collection<Subtask<T>> subtasks = new ConcurrentLinkedQueue<>(); public boolean onComplete(Subtask<T> subtask) { if (subtask.state() == State.SUCCESS) { this.subtasks.add(subtask); } return false; } }
  168. Loom: Where Are We? Virtual Threads Final in 21, improved

    in 24 Scoped Values Final in 25 StructuredConcurrency Preview in 25, 26, 27 191 Copyright © 2026, Oracle and/or its affiliates