Sitemap

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:

  1. Tasks are not related logically.
  2. If one task fails, the other keeps running.
  3. Cancellation is difficult.
  4. Resource leaks are possible.
  5. Debugging becomes harder.

Structured Concurrency treats related concurrent tasks as a single unit of work.

Real-World Example

Suppose an API endpoint needs:

GET /dashboard

To 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 Service

All child tasks belong to the parent request.

Core Class

Java provides:

StructuredTaskScope

Package:

java.util.concurrent

Basic 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 Task

join()

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
SQLException

It rethrows it.

ShutdownOnFailure

Most commonly used scope.

StructuredTaskScope.ShutdownOnFailure

Behavior:

Task A -> Success
Task B -> Failure
Task C -> Running

When B fails:

Task C gets cancelled
Scope terminates
Exception propagated

This 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 immediately

ShutdownOnSuccess

Another built-in policy.

StructuredTaskScope.ShutdownOnSuccess

Used 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 Yahoo

Return 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 model

Hierarchical Task Management

Without Structured Concurrency:

Thread A
Thread B
Thread C
Thread D

No relationship.

With Structured Concurrency:

Dashboard Request
│
├── User Task
├── Order Task
└── Recommendation Task

Like a tree structure.

When the parent ends:

Children end
Resources released

Timeout 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 thrown

Production Use Cases

1. API Aggregation

Frontend Request
│
├── User Service
├── Product Service
└── Pricing Service

Very common in microservices.

2. Parallel Database Queries

Customer
Orders
Invoices

Fetch simultaneously.

3. Search Systems

ElasticSearch Cluster 1
ElasticSearch Cluster 2
ElasticSearch Cluster 3

Take the first successful result.

4. Multi-region Calls

India Region
US Region
Europe Region

Return 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?

Press enter or click to view image in full size

Difference between ShutdownOnFailure and ShutdownOnSuccess?

Press enter or click to view image in full size

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:

  • StructuredTaskScope
  • fork()
  • join()
  • throwIfFailed()
  • ShutdownOnFailure
  • ShutdownOnSuccess
  • 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.

Written by Syed Nazeer

I'm a Java enthusiast, developing passion for technical content creation.