Saltar al contenido
ES EN

Guide to Migrating to Final Structured Concurrency in JDK 28

Practical steps to bring your StructuredTaskScope code to JDK 28 after JEP 543: what's changed since JDK 27, JDK 25 and JDK 26, and how to adapt.

Software engineering

After seven preview rounds, Structured Concurrency is now a final API: JEP 543 was integrated into JDK 28 with no API changes from JDK 27. In this guide, we review how to migrate depending on your starting version, what the resulting code looks like, and where to pay closer attention.

The underlying idea hasn't changed since the JEP 428 incubator: treat a group of subtasks on different threads as a single unit of work. If one fails, the others are canceled, and the parent scope doesn't terminate until all child threads have finished.

What you'll need

A project that already uses StructuredTaskScope and JDK 28 for compiling and testing. Keep the java.util.concurrent package documentation handy and locate any custom joiners and exception handlers around join() in your code.

Diagram of a structured scope with a parent thread launching multiple subtasks and waiting for all to complete
The parent scope does not terminate until all subtasks are complete.

Migration steps

  1. Identify your starting version: if you're on JDK 27, the change is minimal; if you're coming from JDK 25 or 26, code adaptations are required.
  2. In JDK 27: remove --enable-preview from compilation and execution, and recompile against JDK 28.
  3. In JDK 26: replace FailedException with ExecutionException and add the type parameter R_X to your joiner signatures.
  4. Review timeouts: StructuredTaskScope.TimeoutException is gone, and the failure is communicated with a CancelledByTimeoutException cause; also, onTimeout() becomes the abstract method timeout().
  5. Look for uses of Joiner.awaitAll(), which no longer exists, and replace them with the policy you actually need.
  6. Run your test suite and pay special attention to error and cancellation paths.

What the final code looks like

The basic case opens the scope with open(), launches subtasks with fork() (each in a virtual thread by default), and waits with join(), which throws ExecutionException if something fails. When the try block closes, pending subtasks are canceled, and their threads are awaited.

Response handle() throws ExecutionException, InterruptedException {
    try (var scope = StructuredTaskScope.open()) {
        Subtask<String> user = scope.fork(() -> findUser());
        Subtask<Integer> order = scope.fork(() -> fetchOrder());
        scope.join(); // throws ExecutionException if any fail
        return new Response(user.get(), order.get());
    }
}

To set a deadline and a scope name (useful for observability), a configuration is passed to open(). If the deadline expires, the cause of the ExecutionException will be CancelledByTimeoutException, and you can distinguish it with a switch on e.getCause().

try (var scope = StructuredTaskScope.open(
        cf -> cf.withTimeout(Duration.ofSeconds(2)).withName("profile-page"))) {
    Subtask<String> user = scope.fork(() -> findUser());
    Subtask<Integer> order = scope.fork(() -> fetchOrder());
    scope.join();
    return new Response(user.get(), order.get());
}
Diagram of a subtask failing and causing the cancellation of the remaining subtasks in the scope
A subtask failure cancels the others.

Custom Joiners

The third type parameter, R_X, allows your joiner to declare its own exception and for join() to throw it directly, without wrapping it. With a quorum joiner, for example, join() would throw your QuorumException. Its timeout() method must throw that same exception with CancelledByTimeoutException as the cause.

Tip If you're unsure which policy to use, start with the built-in ones: Joiner.allSuccessfulOrThrow() returns a list directly, anySuccessfulOrThrow() returns the first valid result, and allUntil(Predicate) cancels when a condition is met. Write your own joiner only when none of these fit.

A final warning: this guide is based on the JEP description and the InfoQ article on finalization in JDK 28. The linked documentation is early access, so cross-reference the details with the final version of your JDK before considering the migration complete.

Original source: infoq.com

Article generated with AI.larebelion

Kernel

· Head of Technology · Spain

«After seven previews, the best move is to measure how much of your code touches exceptions and joiners, and migrate from there.»

Comentarios

Publicar un comentario