Feedback: FailedException and Joiner

Anthony Vanelverdinghe dev at anthonyv.be
Fri Aug 8 14:18:59 UTC 2025


On 8/8/2025 2:40 PM, Alan Bateman wrote:
>
> On 08/08/2025 12:56, Anthony Vanelverdinghe wrote:
>> Here's my feedback from upgrading my Loom experiments to JDK 25:
>
> Can you provide a summary of your experiments? The most useful 
> feedback is usually from experiments with real world applications and 
> scenarios so it would be interesting to know more about your usages, a 
> bit on what the subtasks do and what they return (the focus on the 
> latter is to get some sense on whether the subtasks return results of 
> the same types or different types).
>
> -Alan


Sure. It's just a single scenario and not a real-world application 
though: a basic grep. So each subtask is a `Callable<T>` that reads a 
chunk of a file and returns a T (a Boolean to indicate whether the chunk 
contained a match, a Long with the number of matches, a record with the 
lines that matched). And then there's a `Collector` to collect the 
results (OR'ing the Booleans, summing the Longs, returning a 
`Stream<String>`).

Previously I had written a `class DefaultSts extends 
StructuredTaskScope` which had a method `results()` returning a `record 
Results<R, S, U>(Reason reason, R successResult, S failedResult, U 
cancelledResult) {}`. So you'd switch on the `reason` to determine what 
to do (this dates back to JDK 19, hence the `cancelledResult`).

So before I had: `var scope = DefaultSts.of(collector)` and `return 
scope.results()`
And now I have: `var scope = 
StructuredTaskScope.open(Joiners.ofThrowing(collector))` and `return 
scope.join()`
which is the same as: `var scope = 
StructuredTaskScope.open(StructuredTaskScope.Joiner.<T>allSuccessfulOrThrow())` 
and `return scope.join().map(Subtask::get).collect(collector)`

Before I would have an exhaustive switch on `Reason` and thus always be 
reminded to do error handling. As mentioned, I was surprised that my 
code compiled without error handling now.
The actual checked exceptions that get thrown from my subtasks are 
`ExecutionException` and `InterruptedException`, from invoking 
`Future::get` on the result of `AsynchronousFileChannel::read`. So if an 
`IOException` were to occur, it would be at 
`FailedException(ExecutionException(IOException))`.

An issue with the current `join` is also that I typically only want to 
deal with checked exceptions and let unchecked exceptions bubble up. So 
I'd need a utility method and something like `catch(FailedException e) { 
var cause = getCauseAndThrowIfUnchecked(e); ... }`.

While I could still have the `Joiner` return something to switch on, 
analogous to what I did previously, `join` is now designed for failure 
handling, so I'd rather use that mechanism. By having two methods, 
`joinChecked` and `joinUnchecked`, a developer  would be able to 
"assert" whether their subtasks throw checked exceptions.

Kind regards, Anthony



More information about the loom-dev mailing list