Mastering Java StructuredTaskScope: Simplify Concurrency with Virtual Threads
JEP 453 integrates StructuredTaskScope into Java 21, offering a preview API that replaces Future with Subtask, enabling lightweight virtual‑thread based concurrent tasks, such as fetching data from multiple servers and automatically cancelling slower calls, while simplifying error handling and improving observability.
JEP 453, Structured Concurrency (preview), has progressed from the Targeted to the Integrated state in JDK 21. The feature originated from an incubating API that incorporated improvements from the previous incubations JEP 428 (JDK 19) and JEP 437 (JDK 20). The only notable change in the current proposal is that
StructuredTaskScope::fork(...)now returns a
Subtaskinstead of a
Future, and it remains a preview feature.
Structured Concurrency in JDK 21 introduces an API that treats a group of related tasks running in different threads as a single unit of work, simplifying error handling, cancellation, reliability, and observability.
Since the introduction of virtual threads, Java provides a new concurrency model based on
StructuredTaskScope. Virtual threads are lightweight and do not need pooling; their stacks can be saved to the heap when blocked and restored on any platform thread.
Example code demonstrates three methods that simulate fetching weather data from three servers with random delays. The goal is to obtain the first successful result and cancel the other two requests via interruption.
public static Weather readFromServerA() throws InterruptedException {
Thread.sleep(RandomUtils.nextLong(1, 100));
return new Weather("Partly Sunny", "server-A");
}
public static Weather readFromServerB() throws InterruptedException {
Thread.sleep(RandomUtils.nextLong(1, 100));
return new Weather("Partly Sunny", "server-B");
}
public static Weather readFromServerC() throws InterruptedException {
Thread.sleep(RandomUtils.nextLong(1, 100));
return new Weather("Partly Sunny", "server-C");
}Using
StructuredTaskScope.ShutdownOnSuccess, the three tasks are forked, the scope is joined, and the first successful
Subtaskresult is retrieved:
public static Weather readWeather() throws ExecutionException, InterruptedException, TimeoutException {
try (var scope = new StructuredTaskScope.ShutdownOnSuccess<Weather>()) {
scope.fork(() -> Weather.readWeatherFromServerA());
scope.fork(() -> Weather.readWeatherFromServerB());
scope.fork(() -> Weather.readWeatherFromServerC());
scope.join();
return scope.result();
}
}The article compares this approach with the traditional
ExecutorService, noting that the latter’s lifecycle is tied to the application, whereas a
StructuredTaskScopeinstance is created per request and destroyed after the method returns, thanks to the lightweight nature of virtual threads.
Running the program shows different servers succeeding while the others fail, illustrating the cancellation behavior. The code can be further simplified using method references.
StructuredTaskScope offers a more convenient and lightweight alternative to ExecutorService, though many frameworks still rely on the latter, presenting opportunities for contributions and upgrades.
Note: This preview feature requires Java 19 or later with the incubator modules enabled.
Java Backend Technology
Focus on Java-related technologies: SSM, Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading. Occasionally cover DevOps tools like Jenkins, Nexus, Docker, and ELK. Also share technical insights from time to time, committed to Java full-stack development!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.