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.
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.

Migration steps
- 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.
- In JDK 27: remove
--enable-previewfrom compilation and execution, and recompile against JDK 28. - In JDK 26: replace
FailedExceptionwithExecutionExceptionand add the type parameterR_Xto your joiner signatures. - Review timeouts:
StructuredTaskScope.TimeoutExceptionis gone, and the failure is communicated with aCancelledByTimeoutExceptioncause; also,onTimeout()becomes the abstract methodtimeout(). - Look for uses of
Joiner.awaitAll(), which no longer exists, and replace them with the policy you actually need. - 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());
}
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.
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



Comentarios
Publicar un comentario