DEV Community

pickuma
pickuma

Posted on Originally published at pickuma.com

What final structured concurrency in JDK 28 means

According to InfoQ's report, JEP 543 Finalizes Structured Concurrency in JDK 28, Ending Seven Preview Rounds, Java's structured concurrency API (StructuredTaskScope) has been integrated into JDK 28 as a final feature. The deciding detail is that the API has no changes from the seventh preview in JDK 27. InfoQ says that if your code already runs on JDK 27, migrating comes down to recompiling without the --enable-preview flag. JDK 28 is scheduled for general availability in March 2027.

The background you need

JEP stands for JDK Enhancement Proposal. It is the design document for a change to the Java platform. A new JDK ships every six months, and each release picks up a set of JEPs.

New Java APIs often pass through trial stages before they become permanent.

  • An incubator API lives in a separate, clearly experimental module.
  • A preview feature is fully specified but not yet locked in. You have to opt in with --enable-preview at both compile time and run time.

Preview code is tied to one exact JDK release. Class files compiled with preview features enabled only run on that same release, and only with the flag turned on. That is why library authors mostly avoid preview APIs. Using one would force every user onto a specific JDK and a special flag.

Project Loom is the OpenJDK effort that brought virtual threads to Java. Virtual threads are lightweight threads that the JVM manages itself, so you can run very large numbers of them cheaply. Structured concurrency is the Loom feature for coordinating those threads.

Here is the problem it solves. Most Java code that fans work out to several threads uses an ExecutorService and collects Future objects. InfoQ points out two weaknesses of that approach:

  • A parent method can return while its subtasks keep running in the background.
  • A subtask's failure only shows up when someone eventually calls Future.get().

In a running service, those weaknesses show up as leaked threads. They also show up as downstream calls that keep going after the request that needed them has already failed, and as thread dumps where nothing tells you which work belongs to which request.

Structured concurrency aims, in the words of the JEP quoted in InfoQ's weekly roundup, to "treat groups of related tasks running in different threads as a single unit of work". InfoQ adds two guarantees. A failure in one subtask cancels the others, and the parent scope cannot exit until all child threads have finished.

An analogy: think of a team lead who sends people out on errands. She cannot leave the office until everyone is back. If one errand fails, she calls the others back.

The analogy breaks on that last step. In Java, cancellation is cooperative and works through thread interruption. A subtask stuck in code that ignores interruption keeps running until it finishes, and the scope waits for it.

The road here was long. InfoQ lists two incubator rounds, JEP 428 in JDK 19 and JEP 437 in JDK 20. Seven previews followed, from JEP 453 in JDK 21 through JEP 533 in JDK 27. InfoQ calls this the longest preview cycle of any Project Loom feature.

What actually changed

The new part is the status. In JDK 28, StructuredTaskScope in java.util.concurrent is a standard API, and no flag is needed.

The API itself is not new. InfoQ says the finalization commit only removes the preview annotations and changes no method signatures. The shape was set in JDK 27, when JEP 533 added a third type parameter. Because of that parameter, join() declares the specific exception its completion policy can throw, instead of a preview-only FailedException.

In practice, you open a scope with StructuredTaskScope.open() inside a try-with-resources block, then:

  • fork subtasks, each on a virtual thread by default;
  • call join() once, to wait for the outcome;
  • let close() run at the end of the block, which cancels unfinished subtasks and waits for their threads to end.

Completion policies are objects called joiners. With the default policy, join() waits for every subtask to succeed, cancels the rest on the first failure, and throws ExecutionException. According to InfoQ, the built-in throwing joiners also throw ExecutionException, the same wrapper Future.get() has long used. A custom joiner can declare its own checked exception instead. Other built-in joiners do things like:

  • return the first success and cancel the rest;
  • cancel the scope once a condition you supply matches.

The two InfoQ pieces describe different stages of the process. The roundup, which covers the week of October 12th, 2026, says JEP 543 was moved from Proposed to Target to Targeted for JDK 28. The main report says the work has been integrated. Targeting normally comes before integration, so the two need not conflict. Neither source gives an integration date.

Teams coming from the JDK 26 preview face real code changes. InfoQ lists these:

  • FailedException is replaced by ExecutionException.
  • Custom joiner signatures gain the new type parameter.
  • StructuredTaskScope.TimeoutException is gone. Timeouts are now reported through a CancelledByTimeoutException cause.
  • Joiner.awaitAll() is no longer available.
  • onTimeout() is replaced by an abstract timeout() method.

What it means for developers who deploy and run apps

If you use the preview on JDK 27, this is a build change, not a code change. Remove --enable-preview everywhere it appears: javac options, test runner settings and launch scripts. In practice that also means Dockerfiles, CI pipelines, and environment variables that inject JVM options. InfoQ says to recompile on a JDK 28 early-access build that includes the change.

If you use the JDK 26 preview, budget time for the API changes listed above, especially around timeouts and custom joiners.

If you never touched it, the change that matters most to you probably arrives through your dependencies. InfoQ calls the bigger shift the one for library and framework maintainers, who can now build on structured concurrency without forcing preview flags on users. Watch the release notes of the web frameworks and clients you rely on.

A few details matter once this runs in production:

  • Observability. You can give a scope a name. Subtasks appear under their parent in JSON thread dumps, which makes stuck requests easier to trace.
  • Context. Subtasks inherit ScopedValue bindings, the newer way to pass request context across threads.
  • Deadlines. A scope can take a timeout that cancels it if it expires before or during join().
  • Fail-fast misuse. Mistakes like closing scopes out of order throw StructureViolationException instead of leaking threads.

If you are staying on JDK 25 for now, InfoQ notes that the velo/structured-concurrency project backports the JEP 543 API to JDK 25. It also ships recipes for moving to the JDK classes after you upgrade.

Classes compiled with preview features on JDK 27 will not run on JDK 28. Rebuild them on JDK 28, and remove --enable-preview from every place it appears, not only from the compiler settings. A stale flag in a launch script or container image is easy to miss.

What we don't know yet

  • Which failure you will see. InfoQ reports that when several subtasks fail at once, the API does not say which failure join() reports. Viktor Klang raised the question during code review. Alan Bateman, the JEP's owner at Oracle, reworded the Javadoc so it promises only that the exception comes from a failed subtask. Do not write error handling that depends on a particular one.
  • Which early-access build to test. InfoQ says to use a JDK 28 early-access build that includes the change, but does not name one. The roundup mentions Build 20 without saying whether it contains JEP 543.
  • The exact release date. March 2027 is a schedule, not a shipped release.
  • Framework timelines. The sources say maintainers can now adopt the API. They do not say who will, or when.

Originally published at pickuma.com. Subscribe to the RSS or follow @pickuma.bsky.social for new reviews.

Top comments (0)