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
StructuredTaskScopeas is - Or extend it for specific behaviors (like handling failure)
try (var scope = new StructuredTaskScope()) {
// your logic here
}
2. Fork tasks using fork()
Use fork() to start subtasks (concurrent threads), returning a Subtask handle.
Subtask task1 = scope.fork(() -> fetchDataFromServiceA());
Subtask task2 = scope.fork(() -> fetchDataFromServiceB());
3. Join all tasks using join()
Wait for all tasks to complete (either normally, exceptionally, or by cancellation).
scope.join();
4. Handle task completion status using throwIfFailed()
After joining, check if any task failed, and throw the exception if needed.
scope.throwIfFailed();
5. Get results from the tasks
Retrieve results from each Subtask using .get().
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.
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
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 userProfileTask = scope.fork(() -> {
Thread.sleep(500); // Simulate delay
return "UserProfile";
});
StructuredTaskScope.Subtask 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.ShutdownOnFailureensures 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
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:
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