Problem report on the usage of Structured Concurrency (5th preview)

Alan Bateman alan.bateman at oracle.com
Fri Sep 26 09:20:57 UTC 2025


On 26/09/2025 07:37, Adam Warski wrote:
> Good morning,
>
> with the release of Java 25, I’ve attempted to migrate my virtual-thread-native, reactive-streaming-like library from Java 21 to Java 25 scopes. So far I’ve been using my own wrapper on StructuredConcurrencyScope, but with that migration I wanted to eliminate it, and just use SCS directly. However, I encountered some problems with SCS’s design; I've summarised them in a blog (https://softwaremill.com/critique-of-jep-505-structured-concurrency-fifth-preview), and then prompted by a discussion on Reddit that followed (https://www.reddit.com/r/java/comments/1nq25yr/critique_of_jep_505_structured_concurrency_fifth/), I’m writing here.
Thanks for writing down your experiences.

(I'll reply to your main points 1 and 2 later as they concern advanced 
use of the API that isn't straight-forward fan-out.)

Just some quick comments on the two smaller points here:


>
> 3) The final two problems are more of nitpicks. First, a `timeout` method can easily be implemented using the machinery of the SCS, without additional configuration parameters. Special-casing for this seems odd, as timeout is only one example from a family of "resiliency" methods, others including retries, repeats etc. These as well, could be implemented on top of virtual threads and SCS as methods, without special support from the SCS API itself.
If you implement your own Joiner with a side channel to this special 
timeout subtask then it would work. However in general, it will be 
policy (and hence Joiner) dependent as to whether a failing subtask will 
cancel the scope.  You'll see what I mean if you try with it a Joiner 
created by the anySuccessfulResultOrThrow factory method. Same thing 
with any Joiner that has a policy that doesn't unconditionally cancel 
when a subtask fails.

Another point on this topic is that the timeout applies to the scope and 
configuring it when creating the scope is good. Additionally, having 
join throwing TimeoutException is useful to distinguish the timeout case 
from other failure outcomes.


>
> 4) The Subtask.get() method is confusing, as it has the semantics of Future.resultNow(), but the nomenclature of Future.get(). Since Future.get() is quite well established, I think it’s reasonable to assume, without prior knowledge of the SCS API, that Subtask.get() is blocking as well. However, it works rather differently. I understand that .get() is a fitting name, however given the existing functionalities in place, I would consider changing the name to something without naming or semantical clashes.
Subtask<T> is a Supplier<T>, and maybe more of the examples should use 
Supplier<T> to avoid anyone thinking Future::get. If someone does 
attempt to use Subtask::get before joining then it will throw of course, 
so they know to use it after join. Ron has long argued that fork should 
just return a Supplier, it's only use is when using subtasks return 
results of different types. If subtasks all return results of the same 
type then the outcome from join is much more useful, no need for 
Supplier::get.

-Alan
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/loom-dev/attachments/20250926/19ce790b/attachment-0001.htm>


More information about the loom-dev mailing list