My two cents… Rust’s error handling is horrible - it is designed to work in 
functional contexts, so like Java streams - the error handling feels “random” 
(and finding out where the error actually occurred is extremely difficult since 
it is a value type).

Java’s Exceptions are for ‘exceptional conditions’ and should not be used for 
flow control (which I don’t think they are in this case - they signify 
unexpected error conditions).


> On Dec 18, 2025, at 3:24 PM, Eric Kolotyluk <[email protected]> wrote:
> 
> My $0.02
> 
> Why are we still relying so heavily on exceptions as a control-flow mechanism?
> 
> Consider the current StructuredTaskScope design:
> 
> The join() method waits for all subtasks to succeed or any subtask to fail.
> The join() method returns null if all subtasks complete successfully.
> It throws StructuredTaskScope.FailedException if any subtask fails, with the 
> exception from the first subtask to fail as the cause.
> 
> This design encodes normal outcomes as null and expected failure modes as 
> exceptions. That choice forces callers into the least informative and least 
> composable error-handling model Java has.
> 
> Returning null for success is especially problematic. null conveys no 
> semantic information, cannot carry context, and pushes correctness checks to 
> runtime. It remains one of Java’s most damaging design decisions, and Loom 
> should not be perpetuating it.
> 
> Optional<T> exists, but it is only a partial solution and does not address 
> error information. In this context, even Optional<Void> would be an 
> improvement over null, but it still leaves failure modeled exclusively as 
> exceptional control flow.
> 
> I also want to be clear that I am not confusing try-with-resources with 
> exceptions. StructuredTaskScope being AutoCloseable is the right design 
> choice for lifetime management and cancellation, and try blocks are the 
> correct mechanism for that. However, scope lifetime and outcome reporting are 
> separable concerns. The use of try does not require that task outcomes be 
> surfaced exclusively via thrown exceptions.
> 
> As a recent Rust convert, the contrast is stark. Rust’s Result<T, E> treats 
> failure as a first-class, explicit outcome, enforced by the type system. Java 
> doesn’t need to abandon exceptions—but it does need to support alternate 
> paradigms where failure is expected, structured, and composable.
> 
> APIs like join() should envision a future beyond “success = null, failure = 
> throw”. Even a simple structured outcome type—success or failure—would be a 
> step forward. Exceptions could remain available for truly exceptional 
> conditions, not routine concurrency outcomes.
> 
> Loom is a rare opportunity to modernize not just how Java runs concurrent 
> code, but how Java models correctness and failure. Re-entrenching null and 
> exception-only outcomes misses that opportunity.
> 
> I’ll stop bloviating now.
> 
> Sincerely,
> Eric Kolotyluk
> 
> 
> 
> On 2025-12-18 1:00 PM, David Alayachew wrote:
> 
>> For 1, the javadoc absolutely does help you. Please read for open.
>> 
>> https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/concurrent/StructuredTaskScope.html#open()
>> 
>> As for verbose, can you go into more detail? This is a traditional builder 
>> pattern addition, so it is literally 1 static method call.
>> 
>> That said, if you dislike a 0 parameter call being forced into being a 2 
>> paramefer call when you need to add timeout, then sure, I think adding an 
>> overload for that static method that takes in the configFunction is 
>> reasonable. I'd support that.
>> 
>> 
>> On Thu, Dec 18, 2025, 3:46 PM Holo The Sage Wolf <[email protected] 
>> <mailto:[email protected]>> wrote:
>>> Hello Loom devs,
>>> Few years ago I experimented in a personal PoC project with 
>>> StructuredConcurrency in Java 19 and I had to stop working on it for 
>>> personal reasons.
>>> 
>>> Recently I came back to the project and updated it to Java 25 and had to 
>>> change my code to the new way the API is built and while doing that I 
>>> noticed a couple of stuff I want to point out:
>>> 
>>> 1. The default Joiner method can't receive timeout
>>> Obviously that is wrong, but the API and JavaDoc don't actually help you. 
>>> Say you start with:
>>>  ```java
>>> try (var scope =  StructuredTaskScope.open()) {
>>>     ...
>>> }
>>> ```
>>> And I want to evolve the code to add timeout, I look at the 
>>> StructuredTaskScope static methods, and won't see any way to do that. After 
>>> reading a bit what StructuredTaskScope.open(Joiner, configFunction) does, I 
>>> will realise that I can set the timeout using the configFunction.
>>> But then I will encounter the problem that I need to provide a Joiner, 
>>> currently the only way to actually get the "no args method"-joiner is to 
>>> look at the source code of the method, see which Joiner it uses and copy 
>>> that into my method to get:
>>>  ```java
>>> try (var scope =  
>>> StructuredTaskScope.open(Joiner.awaitAllSuccessfulOrThrow(), (conf) -> 
>>> ...)) {
>>>     ...
>>> }
>>> ```
>>> Not only is this a lot of work to do something very simple, there is a high 
>>> chance that people who start learning concurrency will want to use timeout 
>>> before they even know what the Joiner object is.
>>> 
>>> 2. Changing only the timeout is "verbose".
>>> I can only talk from my experience, so I may have the wrong impression, but 
>>> I feel like setting timeout is orders of magnitude more common than 
>>> changing the default ThreadFactory (especially when using virtual threads) 
>>> or setting a name.
>>> I feel like adding a couple of overloads of the open method that takes only 
>>> an extra parameter of duration will be convenient:
>>> > StructuredTaskScope.open()
>>> > StructuredTaskScope.open(Duration timeout)
>>> > StructuredTaskScope.open(Joiner joiner)
>>> > StructuredTaskScope.open(Joiner joiner, Duration timeout)
>>> > StructuredTaskScope.open(Joiner joiner, Function<Configuration, 
>>> > Configuration> configFunction)
>>> 

Reply via email to