Feedback on Structured Concurrency (JEP 525, 6th Preview)
forax at univ-mlv.fr
forax at univ-mlv.fr
Sun Oct 12 16:39:31 UTC 2025
> From: "Robert Engels" <robaho at me.com>
> To: "Remi Forax" <forax at univ-mlv.fr>
> Cc: "Jige Yu" <yujige at gmail.com>, "loom-dev" <loom-dev at openjdk.org>
> 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 <forax at univ-mlv.fr> wrote:
>>> From: "Jige Yu" <yujige at gmail.com>
>>> To: "loom-dev" <loom-dev at openjdk.org>
>>> 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
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/loom-dev/attachments/20251012/2cceb2c6/attachment.htm>
More information about the loom-dev
mailing list