Feedback: FailedException and Joiner

Anthony Vanelverdinghe dev at anthonyv.be
Fri Aug 8 11:56:13 UTC 2025


Here's my feedback from upgrading my Loom experiments to JDK 25:

I suddenly realized the code compiled, even though I hadn't implemented 
any error handling yet: turns out this is done via FailedException now, 
which is a RuntimeException.
Personally, I'd much rather have a checked exception here: the Subtasks 
might be Callables that throw checked exceptions like IOException and 
SQLException, and I'd like the compiler to notify me that I haven't 
dealt with them yet. In fact, such exceptions are likely, since 
structured concurrency is often used for IO-bound tasks. Also note that 
`j.u.c.ExecutionException` is a checked exception as well.
That being said: in cases where the scope can only ever throw an 
unchecked exception, having to deal with a checked exception is a nuisance.

To improve this, I propose replacing `join` with `joinUnchecked throws 
UncheckedFailedException` and `joinChecked throws FailedException, 
UncheckedFailedException`. That way the developer always has to make a 
conscious choice. The former would work just like `join` does today. The 
latter would check the `Throwable` coming from `result()`: if it's a 
checked exception, it would throw `FailedException`, else it would throw 
`UncheckedFailedException`. (Note that we could reuse 
`j.u.c.ExecutionException` here and simply introduce 
`j.u.c.UncheckedExecutionException`.)

For `StructuredTaskScope.Joiner` I propose to:

* use `Success` instead of `Successful` in the method names, to match 
`Subtask.State.SUCCESS`
* add `all` as the result-returning equivalent of `awaitAll`
* have `allSuccessOrThrow` return `Stream<T>`, because in my experience 
it's only ever followed by `.map(Subtask::get)` (and `anySuccessOrThrow` 
also returns just `T` instead of `Subtask<T>`)
* change the parameter type of `allUntil` to `Predicate<? super 
StructuredTaskScope.Subtask<? extends T>>` to allow passing in an 
existing `Predicate<Object>` (e.g., `o -> true`)

Also please consider adding factory methods that take a `Collector` 
(possibly with an overload that takes a `Predicate isDone` as well) or 
`Gatherer` like below. This allows type inference to work its magic. 
With the current `allSuccessOrThrow()`, you have to provide the type 
argument or you end up with `join` returning a `Stream<Subtask<Object>>`.

* `<T, R> StructuredTaskScope.Joiner<T, R> allSuccessOrThrow(Collector<? 
super T, ?, ? extends R> collector)`
* `<T, R> StructuredTaskScope.Joiner<T, Stream<R>> 
allSuccessOrThrow(Gatherer<? super T, ?, ? extends R> gatherer)`

Kind regards
Anthony



More information about the loom-dev mailing list