Structured Concurrency in Java 21 is a powerful feature, designed to simplify concurrent programming and make it safer. If you’re new to this concept, don’t worry β this guide breaks it down in simple terms with full examples!

π What is Structured Concurrency in Java 21?
Traditionally, managing multiple threads in Java has been tricky β you start threads, forget to stop them, or struggle with error handling. Structured Concurrency changes that by treating multiple tasks as a single unit of work.
Think of it like this:
βIf one task fails, cancel the others. If all succeed, collect the results.β
It brings a more organized, readable, and safer way to write concurrent code.
π§° New API: StructuredTaskScope
StructuredTaskScope
is a class introduced in Java 21 as part of the Structured Concurrency API (in java.util.concurrent
). It provides a clean and organized way to manage concurrent tasks that are logically related and need to be started, waited upon, and cancelled together.
1. Create a subclass or use StructuredTaskScope
directly
You can either:
- Use
StructuredTaskScope
as is - Or extend it for specific behaviors (like handling failure)
1 2 3 4 5 |
try (var scope = new StructuredTaskScope<String>()) { // your logic here } |
2. Fork tasks using fork()
Use fork()
to start subtasks (concurrent threads), returning a Subtask
handle.
1 2 3 4 |
Subtask<String> task1 = scope.fork(() -> fetchDataFromServiceA()); Subtask<String> task2 = scope.fork(() -> fetchDataFromServiceB()); |
3. Join all tasks using join()
Wait for all tasks to complete (either normally, exceptionally, or by cancellation).
1 2 3 |
scope.join(); |
4. Handle task completion status using throwIfFailed()
After joining, check if any task failed, and throw the exception if needed.
1 2 3 |
scope.throwIfFailed(); |
5. Get results from the tasks
Retrieve results from each Subtask
using .get()
.
1 2 3 4 |
String resultA = task1.get(); String resultB = task2.get(); |
6. Auto-close with try-with-resources
StructuredTaskScope
implements AutoCloseable
, so it ensures proper cleanup when used in a try
block.
1 2 3 4 5 |
try (var scope = new StructuredTaskScope<>()) { // tasks } |
π― Benefits of StructuredTaskScope
- Scoped lifetimes: Tasks live and die with the scope
- Error propagation: If one task fails, the whole operation can be aborted
- Cancellation support
- Easier debugging and resource management
β Basic Example: Running Two Tasks Concurrently
Letβs write a Java program that runs two tasks concurrently using Structured Concurrency.
π File: StructuredExample.java
Package: com.kscodes
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
package com.kscodes; import java.util.concurrent.ExecutionException; import java.util.concurrent.StructuredTaskScope; public class StructuredExample { public static void main(String[] args) { try { String result = fetchUserProfileAndSettings(); System.out.println("Final Result: " + result); } catch (InterruptedException | ExecutionException e) { System.out.println("Error: " + e.getMessage()); } } public static String fetchUserProfileAndSettings() throws InterruptedException, ExecutionException { try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { StructuredTaskScope.Subtask<String> userProfileTask = scope.fork(() -> { Thread.sleep(500); // Simulate delay return "UserProfile"; }); StructuredTaskScope.Subtask<String> userSettingsTask = scope.fork(() -> { Thread.sleep(1000); // Simulate longer delay return "UserSettings"; }); scope.join() // Wait for all tasks .throwIfFailed(); // Throw if any failed String profile = userProfileTask.get(); String settings = userSettingsTask.get(); return profile + " + " + settings; } } } |
π§ How This Works
StructuredTaskScope.ShutdownOnFailure
ensures all tasks are canceled if one fails.scope.fork(...)
runs a task.scope.join()
waits for all tasks to complete.scope.throwIfFailed()
throws an exception if any task failed.task.get()
retrieves the result.
β What if One Task Fails?
Hereβs an example that shows failure handling.
π File: StructuredFailureExample.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
package com.kscodes; import java.util.concurrent.ExecutionException; import java.util.concurrent.StructuredTaskScope; public class StructuredFailureExample { public static void main(String[] args) { try { runTasks(); } catch (Exception e) { System.out.println("Caught Exception: " + e.getMessage()); } } public static void runTasks() throws InterruptedException, ExecutionException { try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { scope.fork(() -> { Thread.sleep(300); return "Success"; }); scope.fork(() -> { Thread.sleep(100); throw new RuntimeException("Something went wrong!"); }); scope.join().throwIfFailed(); } } } |
π§Ύ Output:
1 2 3 |
Caught Exception: java.lang.RuntimeException: Something went wrong! |
As soon as one task fails, the other is automatically canceled β keeping your program safe and efficient.
π Benefits of Structured Concurrency
- β Easier to understand concurrent code
- β Safer: no forgotten threads
- β Built-in error handling
- β Ideal for working with Virtual Threads (Java 21 feature)
π‘ Final Thoughts
Structured Concurrency in Java 21 is a game-changer. It brings clarity, safety, and simplicity to concurrent programming. Whether you’re building web services or background processing, this new approach helps you write better code β faster.
Reference : Details of Structured Concurrency from Java docs