Structured Concurrency is a programming model introduced in Project Loom to manage concurrent tasks in a structured way. Instead of spawning threads or tasks that run independently and risk leaking resources, Structured Concurrency ensures that concurrent tasks are grouped together under a scope. Once the scope finishes, all tasks inside are either completed or canceled. This makes concurrent programming safer, more predictable, and easier to reason about.
How does it work
The idea is similar to structured programming: just like every block of code has a clear start and end, structured concurrency enforces that concurrent tasks have a well-defined lifetime tied to their parent scope.
In Java, this is implemented through the StructuredTaskScope
API. A scope is opened, tasks are forked inside it, and when the scope is closed, the runtime ensures that no tasks are left running beyond the lifetime of that scope.
Why use it
- Predictable lifecycle of tasks, which avoids thread leaks.
- Automatic cleanup of unfinished or failed tasks.
- Easier error handling: exceptions from concurrent tasks can be collected and propagated.
- Composability: different policies (
ShutdownOnFailure
,ShutdownOnSuccess
) define how the group behaves. - Works seamlessly with virtual threads, making high concurrency much simpler without the complexity of reactive programming.
When to use it and when not
Use it when
- You need to run multiple tasks concurrently and wait for all or some of them.
- You want clear cancellation and failure semantics (if one task fails, others should stop).
- You are building APIs or services where reliability and resource safety are critical.
Avoid it when
- You need extremely long-lived tasks or daemons that should outlive their callers.
- You are already using a different concurrency model (e.g., reactive streams) and do not want to mix models.
- Your workload does not benefit from concurrency (sequential execution may be simpler and faster).
Example with StructuredTaskScope
Example: Basic StructuredTaskScope
import java.util.concurrent.*;
public class StructuredConcurrencyExample {
public static void main(String[] args) throws Exception {
try (var scope = new StructuredTaskScope<String>()) {
Future<String> task1 = scope.fork(() -> fetchUserData());
Future<String> task2 = scope.fork(() -> fetchOrderData());
scope.join(); // Wait for all tasks
scope.throwIfFailed(); // Propagate exceptions
String result = task1.resultNow() + " | " + task2.resultNow();
System.out.println("Result: " + result);
}
}
static String fetchUserData() throws InterruptedException {
Thread.sleep(500);
return "User Data";
}
static String fetchOrderData() throws InterruptedException {
Thread.sleep(700);
return "Order Data";
}
}
Example: StructuredTaskScope.ShutdownOnFailure
import java.util.concurrent.*;
public class ShutdownOnFailureExample {
public static void main(String[] args) throws Exception {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Future<String> task1 = scope.fork(() -> fetchData());
Future<String> task2 = scope.fork(() -> { throw new RuntimeException("Failure"); });
scope.join(); // Wait until all tasks complete or one fails
scope.throwIfFailed(); // Throws the exception from the failed task
}
}
static String fetchData() throws InterruptedException {
Thread.sleep(500);
return "Data";
}
}
Here, once one task fails, all others are canceled automatically.
Example: StructuredTaskScope.ShutdownOnSuccess
import java.util.concurrent.*;
public class ShutdownOnSuccessExample {
public static void main(String[] args) throws Exception {
try (var scope = new StructuredTaskScope.ShutdownOnSuccess<String>()) {
scope.fork(() -> { Thread.sleep(700); return "Slow Result"; });
scope.fork(() -> { Thread.sleep(200); return "Fast Result"; });
scope.join(); // Wait until first successful result
String result = scope.result(); // Returns the first success
System.out.println("Result: " + result);
}
}
}
Here, as soon as one task succeeds, the other tasks are canceled.
How does it terminate all Virtual Threads
Structured concurrency scopes are designed to be parents of the tasks they create. When the scope is closed (via try-with-resources or explicit closing), all unfinished tasks in that scope are canceled. Since these tasks typically run in virtual threads, canceling them means their virtual threads are interrupted, and the runtime ensures they terminate gracefully. This avoids leaving orphaned or runaway threads.
It is not final yet
As of September 2025, Structured Concurrency in Java is still in preview. The API (StructuredTaskScope
) is evolving, and some details may change in future releases before it becomes standard. Developers should use it to experiment and prepare for the future but be mindful that syntax and behavior might shift in upcoming JDK versions.
Top comments (0)