Structured concurrency (preview)

Multithreading · lesson 29 of 38 · 3 min read

Treat concurrent subtasks like a block of code: they start together, fail together and end together.

Open this lesson in the learning hub

Key points

  • Loose submit() calls leak. When one task fails, its siblings keep running with nobody watching them.
  • StructuredTaskScope ties subtasks to a block, so nothing you fork can outlive the try-with-resources.
  • ShutdownOnFailure cancels the remaining subtasks as soon as one fails, then throwIfFailed rethrows.
  • ShutdownOnSuccess is the opposite race: keep the first answer that arrives and cancel the rest.
  • The stack trace keeps the parent-child relation, so a failure reads like an ordinary nested call.
  • It is a preview API in Java 21, so it needs --enable-preview to compile and run.

Example

import java.util.concurrent.StructuredTaskScope;   // Java 21 preview: run with --enable-preview

public class Main {
    record User(String name) { }
    record Order(int count) { }

    public static void main(String[] args) throws Exception {
        try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
            StructuredTaskScope.Subtask<User> user = scope.fork(() -> new User("ada"));
            StructuredTaskScope.Subtask<Order> order = scope.fork(() -> new Order(3));

            scope.join();              // wait for both children
            scope.throwIfFailed();     // if either failed, the other was already cancelled

            System.out.println(user.get().name() + " has " + order.get().count() + " orders");
        }   // close() guarantees no child outlives this block
    }
}

Subtasks should live and die inside the block that started them.

This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Multithreading course, and every lesson in it is listed on the Multithreading contents page.