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