import java.time.Duration; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.StructuredTaskScope; import java.util.concurrent.StructuredTaskScope.Joiner; import java.util.concurrent.StructuredTaskScope.Subtask; /** * JEP 533, Structured Concurrency (seventh preview), written against the JDK 27 API. * *

The headline change from JDK 26 is that {@code StructuredTaskScope} and {@code Joiner} gained a * third type parameter, {@code R_X}: the exception {@code join()} throws. That lets a scope throw YOUR * exception type rather than a generic {@code FailedException}. Compare broken/StructuredScope26.java, * which is JDK 26 code that no longer compiles. Explained in docs/07-structured-concurrency.md. * *

Compile and run with --enable-preview. */ public class StructuredDemo { /** The application's own failure type: what R_X lets us surface. */ static class OrderFailed extends Exception { OrderFailed(Throwable cause) { super("order failed: " + cause.getMessage(), cause); } } static String fetchPrice() throws InterruptedException { Thread.sleep(50); return "price=42"; } static String fetchStock() throws InterruptedException { Thread.sleep(80); return "stock=7"; } static String failingCall() { throw new IllegalStateException("inventory service is down"); } public static void main(String[] args) throws Exception { allSuccessful(); yourOwnExceptionType(); timeout(); } /** The default joiner: all subtasks must succeed, failure surfaces as ExecutionException. */ static void allSuccessful() throws Exception { System.out.println("--- allSuccessfulOrThrow(): results as a List, failures as ExecutionException"); try (var scope = StructuredTaskScope.open(Joiner.allSuccessfulOrThrow())) { scope.fork(StructuredDemo::fetchPrice); scope.fork(StructuredDemo::fetchStock); List results = scope.join(); System.out.println("results = " + results); Check.that(results.equals(List.of("price=42", "stock=7")), "join() returns the subtask results in fork order"); } try (var scope = StructuredTaskScope.open(Joiner.allSuccessfulOrThrow())) { scope.fork(StructuredDemo::fetchPrice); scope.fork(StructuredDemo::failingCall); scope.join(); Check.that(false, "unreachable"); } catch (ExecutionException e) { System.out.println("caught " + e.getClass().getName() + " with cause " + e.getCause()); Check.that(e.getCause() instanceof IllegalStateException, "a failing subtask surfaces as ExecutionException(cause)"); } } /** New in 27: hand the joiner a function and join() throws YOUR type. */ static void yourOwnExceptionType() throws InterruptedException { System.out.println("--- allSuccessfulOrThrow(Function): join() throws the exception type you choose"); try (var scope = StructuredTaskScope.open(Joiner.allSuccessfulOrThrow(OrderFailed::new))) { scope.fork(StructuredDemo::fetchPrice); scope.fork(StructuredDemo::failingCall); scope.join(); // declared: throws OrderFailed, InterruptedException Check.that(false, "unreachable"); } catch (OrderFailed e) { System.out.println("caught " + e.getClass().getSimpleName() + ": " + e.getMessage()); Check.that(e.getCause() instanceof IllegalStateException, "the mapped exception wraps the original failure"); } } /** Timeouts are now a configuration option; a Joiner decides what a timeout means. */ static void timeout() throws Exception { System.out.println("--- withTimeout(...): what happens when the scope runs out of time"); try (var scope = StructuredTaskScope.open(Joiner.allSuccessfulOrThrow(), cfg -> cfg.withTimeout(Duration.ofMillis(100)))) { Subtask slow = scope.fork(() -> { Thread.sleep(2_000); return "too late"; }); scope.join(); Check.that(false, "unreachable"); } catch (Exception e) { System.out.println("caught " + e.getClass().getName()); System.out.println(" cause: " + e.getCause()); Check.that(e.getCause() instanceof StructuredTaskScope.CancelledByTimeoutException || e instanceof StructuredTaskScope.CancelledByTimeoutException, "a timeout is reported as CancelledByTimeoutException"); } } }