> From: "Robert Engels" <[email protected]>
> To: "Remi Forax" <[email protected]>
> Cc: "Jige Yu" <[email protected]>, "loom-dev" <[email protected]>
> Sent: Sunday, October 12, 2025 3:52:02 PM
> Subject: Re: Feedback on Structured Concurrency (JEP 525, 6th Preview)

> Checked exceptions are great. Just need to understand how to use them 
> properly.
> I think the Swift way of addressing it is fantastic.

see [ https://www.artima.com/articles/the-trouble-with-checked-exceptions | 
https://www.artima.com/articles/the-trouble-with-checked-exceptions ] 

regards, 
Rémi 

>> On Oct 12, 2025, at 8:28 AM, Remi Forax <[email protected]> wrote:

>>> From: "Jige Yu" <[email protected]>
>>> To: "loom-dev" <[email protected]>
>>> Sent: Sunday, October 12, 2025 7:32:33 AM
>>> Subject: Feedback on Structured Concurrency (JEP 525, 6th Preview)

>>> Hi Project Loom.

>> Hello,

>>> First and foremost, I want to express my gratitude for the effort that has 
>>> gone
>>> into structured concurrency. API design in this space is notoriously 
>>> difficult,
>>> and this feedback is offered with the greatest respect for the team's work 
>>> and
>>> in the spirit of collaborative refinement.

>>> My perspective is that of a developer looking to use Structured Concurrency 
>>> for
>>> common, IO-intensive fan-out operations. My focus is to replace everyday 
>>> async
>>> callback hell, or reactive chains with something simpler and more readable.

>>> It will lack depth in the highly specialized concurrent programming area. 
>>> And I
>>> acknowledge this viewpoint may bias my feedback.
>>> High-Level Impression

>>> From this perspective, the current API feels imperative and more complex 
>>> for the
>>> common intended use cases than necessary. It introduces significant 
>>> cognitive
>>> load through its stateful nature and manual lifecycle management.
>>> Specific Points of Concern

>>>     1.

>>> Stateful and Imperative API: The API imposes quite some "don't do this at 
>>> time
>>> X" rules. Attempting to fork() after join() leads to a runtime error;
>>> forgetting to call join() is another error; and the imperative fork / join
>>> sequence is more cumbersome than a declarative approach would be. None of 
>>> these
>>> are unmanageable though.

>> I had a similar feeling the first time I used the API, but once you play with
>> it, it kind of make sense.
>> The API can be used when all tasks are different (concurrent tasks) or when 
>> all
>> task are the same (parallel tasks), a more functional API will only work with
>> the latter.

>>>     1.

>>> Challenging Exception Handling: The exception handling model is tricky:
>>>         *

>>> Loss of Checked Exception Compile-Time Safety: FailedException is 
>>> effectively an
>>> unchecked wrapper that erases checked exception information at compile time.
>>> Migrating from sequential, structured code to concurrent code now means 
>>> losing
>>> valuable compiler guarantees.

>> You can propagate the exceptions but it makes the API clunkier (one more type
>> variable everywhere) and do not solve the fundamental problem that you do not
>> want to merge the control flow of an exception that comes from a callable 
>> with
>> one that comes from STS.join(). By example, distinguishing if an
>> InterruptedException is raised because the main thread is interrupted or if 
>> one
>> of the callable is interrupted (and this is the same will all runtime
>> exceptions).

>>>     1.
>>>         *

>>> No Help For Exception Handling: For code that wants to catch and handle 
>>> these
>>> exceptions, it's the same story of using instanceof on the getCause(), 
>>> again,
>>> losing all compile-time safety that was available in equivalent sequential
>>> code.

>> see above

>>>     1.
>>>         *

>>> Burdensome InterruptedException Handling: The requirement for the caller to
>>> handle or propagate InterruptedException from join() will add room for 
>>> error as
>>> handling InterruptedException is easy to get wrong: one can forget to call
>>> currentThread().interrupt(). Or, if the caller decides to declare throws
>>> InterruptedException , the signature propagation becomes viral.

>> Having InterruptedException not being runtime exception is a pain. But this 
>> is a
>> pain for all blocking methods.
>> And BTW, you can also wrap it into a runtime exception (usually
>> UncheckedIOException/IOError) which works better than
>> currentThread().interrupt() because you do not loose the context (the stack
>> trace) and avoid the problem of the signature propagation.

>> Perhaps at some point in the future, all exceptions will be runtime 
>> exceptions
>> (like in Kotlin or C#) but this is a Java problem not a problem of the STS 
>> API.

>>>     1.
>>>         *

>>> Default Exception Swallowing: The AnySuccessOrThrow policy swallows all
>>> exceptions by default, including critical ones like NullPointerException ,
>>> IllegalArgumentException , or even an Error . This makes it dangerously 
>>> easy to
>>> mask bugs that should be highly visible. There is no straightforward 
>>> mechanism
>>> to inspect these suppressed exceptions or fail on specific, unexpected 
>>> types.

>> The straightforward mechanism is to inspect the Subtasks that keep that
>> information (if available).

>>>     1.

>>> Conflated API Semantics: The StructuredTaskScope API unifies two very 
>>> different
>>> concurrency patterns—"gather all" ( allSuccessfulOrThrow ) and "race to 
>>> first
>>> success" ( anySuccessfulResultOrThrow )—under a single class but with 
>>> different
>>> interaction models for the same method.
>>>         *

>>> In the "gather all" pattern ( allSuccessfulOrThrow ), join() returns void . 
>>> The
>>> callsite should use subtask.get() to retrieve results.
>>>         *

>>> In the "race" pattern ( anySuccessfulResultOrThrow ), join() returns the 
>>> result
>>> ( R ) of the first successful subtask directly. The developer should not 
>>> call
>>> get() on individual subtasks. Having the join()+subtask.get() method spec'ed
>>> conditionally (which method to use and how depends on the actual policy) 
>>> feels
>>> like a minor violation of LSP and is a source of confusion. It may be an
>>> indication of premature abstraction.

>> I kind agree on this one, i.e. i would like the semantics of when to stop the
>> STS and the semantics of getting all subtaks or not to be separated given 
>> there
>> are separated concern.

>>>     1.

>>> Overly Complex Customization: The StructuredTaskScope.Policy API, while
>>> powerful, feels like a potential footgun. The powerful lifecycle callback
>>> methods like onFork(), onComplete(), onTimeout() may lower the barrier to
>>> creating intricate, framework-like abstractions that are difficult to reason
>>> about and debug.

>> yes, especially if you try to do reduce to a value (like a Collector) inside 
>> the
>> Joiner but this is called out by the documentation.

>> I will answer the rest of the mail, in a new message.

>> Rémi

Reply via email to