Structured Concurrency
Structured Concurrency in Java (Everything You Need to Know)
Structured Concurrency is a modern concurrency model introduced as a preview feature in Java (starting with Java 21) to simplify managing multiple concurrent tasks.
It is part of the effort led by Ron Pressler under OpenJDK’s Project Loom.
Why Was Structured Concurrency Introduced?
Traditional Java concurrency has several problems:
ExecutorService executor = Executors.newFixedThreadPool(2);Future<User> userFuture = executor.submit(this::getUser);
Future<Order> orderFuture = executor.submit(this::getOrders);User user = userFuture.get();
Order order = orderFuture.get();
Problems:
- Tasks are not related logically.
- If one task fails, the other keeps running.
- Cancellation is difficult.
- Resource leaks are possible.
- Debugging becomes harder.
Structured Concurrency treats related concurrent tasks as a single unit of work.
Real-World Example
Suppose an API endpoint needs:
GET /dashboardTo build the response it needs:
- User Profile Service
- Order Service
- Recommendation Service
Instead of:
Future<User> user;
Future<Orders> orders;
Future<Recommendations> recs;You create a task scope:
Dashboard Request
│
├── User Service
├── Order Service
└── Recommendation ServiceAll child tasks belong to the parent request.
Core Class
Java provides:
StructuredTaskScopePackage:
java.util.concurrentBasic Example
try (var scope =
new StructuredTaskScope.ShutdownOnFailure()) {Subtask<String> user =
scope.fork(() -> fetchUser()); Subtask<String> orders =
scope.fork(() -> fetchOrders()); scope.join();
scope.throwIfFailed(); System.out.println(user.get());
System.out.println(orders.get());
}
What Happens Internally?
fork()
Creates a child task.
scope.fork(() -> fetchUser());Think of it as:
Parent Task
|
+--> Child Taskjoin()
Waits for all child tasks.
scope.join();Equivalent conceptually to:
future1.get();
future2.get();
future3.get();but managed as a group.
throwIfFailed()
Checks whether any child task failed.
scope.throwIfFailed();If any child throws an exception:
RuntimeException
IOException
SQLExceptionIt rethrows it.
ShutdownOnFailure
Most commonly used scope.
StructuredTaskScope.ShutdownOnFailureBehavior:
Task A -> Success
Task B -> Failure
Task C -> RunningWhen B fails:
Task C gets cancelled
Scope terminates
Exception propagatedThis prevents wasted work.
Example
try (var scope =
new StructuredTaskScope.ShutdownOnFailure()) {var user =
scope.fork(() -> getUser()); var payment =
scope.fork(() -> getPayment()); scope.join();
scope.throwIfFailed(); return combine(user.get(), payment.get());
}
If payment fails:
User task cancelled
Request fails immediatelyShutdownOnSuccess
Another built-in policy.
StructuredTaskScope.ShutdownOnSuccessUsed when you only need the first successful result.
Get Syed Nazeer’s stories in your inbox
Join Medium for free to get updates from this writer.
Example:
Search Google
Search Bing
Search YahooReturn whichever responds first.
try (var scope =
new StructuredTaskScope.ShutdownOnSuccess<String>()) {scope.fork(() -> searchA());
scope.fork(() -> searchB());
scope.fork(() -> searchC()); scope.join(); return scope.result();
}
Comparison with CompletableFuture
CompletableFuture
CompletableFuture<User> user =
supplyAsync(this::getUser);CompletableFuture<Order> order =
supplyAsync(this::getOrder);return user.thenCombine(order, Dashboard::new);
Problems:
- Complex chains
- Hard cancellation
- Task relationships are implicit
- Stack traces can be difficult
Structured Concurrency
try (var scope =
new StructuredTaskScope.ShutdownOnFailure()) {var user = scope.fork(this::getUser);
var order = scope.fork(this::getOrder); scope.join();
scope.throwIfFailed(); return new Dashboard(
user.get(),
order.get());
}
Advantages:
- Readable
- Easier debugging
- Better cancellation
- Clear parent-child relationship
Relationship with Virtual Threads
Structured Concurrency works extremely well with:
Virtual Threads
Example:
Thread.ofVirtual().start(...);Every forked task can run in a virtual thread.
Benefits:
Thousands of tasks
Low memory usage
Simple programming modelHierarchical Task Management
Without Structured Concurrency:
Thread A
Thread B
Thread C
Thread DNo relationship.
With Structured Concurrency:
Dashboard Request
│
├── User Task
├── Order Task
└── Recommendation TaskLike a tree structure.
When the parent ends:
Children end
Resources releasedTimeout Example
try (var scope =
new StructuredTaskScope.ShutdownOnFailure()) {var user = scope.fork(this::getUser); scope.joinUntil(
Instant.now().plusSeconds(2)); scope.throwIfFailed(); return user.get();
}
If the timeout is reached:
Task cancelled
Timeout exception thrownProduction Use Cases
1. API Aggregation
Frontend Request
│
├── User Service
├── Product Service
└── Pricing ServiceVery common in microservices.
2. Parallel Database Queries
Customer
Orders
InvoicesFetch simultaneously.
3. Search Systems
ElasticSearch Cluster 1
ElasticSearch Cluster 2
ElasticSearch Cluster 3Take the first successful result.
4. Multi-region Calls
India Region
US Region
Europe RegionReturn the fastest response.
Interview Questions
What is Structured Concurrency?
A concurrency model where related tasks are grouped into a single scope and managed as one unit.
Why is it better than ExecutorService?
- Automatic cancellation
- Better error propagation
- Clear lifecycle management
- Parent-child task relationship
Difference between StructuredTaskScope and CompletableFuture?
Difference between ShutdownOnFailure and ShutdownOnSuccess?
Important Version Information
Structured Concurrency has been evolving through multiple preview/incubator releases in Java. If you’re preparing for interviews on Java 21+, focus on:
StructuredTaskScopefork()join()throwIfFailed()ShutdownOnFailureShutdownOnSuccess- Integration with Virtual Threads
These are the concepts most commonly discussed in modern Java backend interviews, especially for senior developers working with microservices and Project Loom features.






