Saturday, May 03, 2025

Java 24: Structured Concurrency

With Java 24, Structured Concurrency moves closer to becoming a first-class feature in the Java platform. This is currently a preview language feature.

Traditional concurrency in Java often results in fragmented and error-prone code, where related threads are launched independently and can be hard to manage or coordinate. For example, to fetch a user and order in parallel, and then process the results, you would typically use an ExecutorService as shown below:

ExecutorService executor = Executors.newFixedThreadPool(2);
Future<String> userFuture = executor.submit(() -> fetchUser());
Future<String> orderFuture = executor.submit(() -> fetchOrder());
String user = userFuture.get();   // blocks until user is fetched
String order = orderFuture.get(); // blocks until order is fetched
String result = process(user, order);

The downsides of the above approach are:

  • If one task fails, the other continues unless manually cancelled
  • The executor and tasks outlive the method unless explicitly shut down
  • You must manage the executor, handle exceptions, and ensure cleanup

Structured Concurrency abstracts much of this complexity, allowing you to focus on what your code is doing rather than how to coordinate threads. It enforces a hierarchical structure, in which tasks spawned together must complete together, much like local variables within a method.

StructuredTaskScope
Here is an example of using the StructuredTaskScope API:

try (var scope = new StructuredTaskScope<String>()) {
  Subtask<String> userTask = scope.fork(() -> fetchUser());
  Subtask<String> orderTask = scope.fork(() -> fetchOrder());

  scope.join(); // Wait for all subtasks to complete

  String user = userTask.get();
  String order = orderTask.get();

  System.out.println("user: " + user);
  System.out.println("order: " + order);
}

StructuredTaskScope has two subclasses, ShutdownOnSuccess and ShutdownOnFailure, to control how the scope reacts to task completion or failure.

StructuredTaskScope.ShutdownOnFailure
With this policy, if any task fails, the scope cancels the remaining tasks, and propagates the exception when throwIfFailed() is called.

try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
  Subtask<String> userTask = scope.fork(() -> fetchUser());
  Subtask<String> orderTask = scope.fork(() -> fetchOrder());

  // wait for all subtasks to complete, or one to fail
  scope.join();
  
  // throw if any subtask failed 
  scope.throwIfFailed();

  String user = userTask.get();
  String order = orderTask.get();

  System.out.println("user: " + user);
  System.out.println("order: " + order);
}

StructuredTaskScope.ShutdownOnSuccess
This policy is the opposite — it stops once one task succeeds, cancelling the others. It's great when you want the first successful result and don't care about the rest.

try (var scope = new StructuredTaskScope.ShutdownOnSuccess<String>()) {
  scope.fork(() -> fetchFromPrimary());
  scope.fork(() -> fetchFromBackup());

  // wait for any subtask to complete, or all to fail
  scope.join();

  // get the result of the first task that completed successfully,
  // or throw an exception if none did
  System.out.println(scope.result()); 
}

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.