<html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head><body style="overflow-wrap: break-word; -webkit-nbsp-mode: space; line-break: after-white-space;">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).<div><br></div><div>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).</div><div><br></div><div><div><br><blockquote type="cite"><div>On Dec 18, 2025, at 3:24 PM, Eric Kolotyluk <eric@kolotyluk.net> wrote:</div><br class="Apple-interchange-newline"><div>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<div>
My $0.02<br>
<br>
Why are we still relying so heavily on exceptions as a control-flow
mechanism?<br>
<br>
Consider the current StructuredTaskScope design:<br>
<br>
The join() method waits for all subtasks to succeed or any subtask
to fail.<br>
The join() method returns null if all subtasks complete
successfully.<br>
It throws StructuredTaskScope.FailedException if any subtask fails,
with the exception from the first subtask to fail as the cause.<br>
<br>
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.<br>
<br>
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.<br>
<br>
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.<br>
<br>
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.<br>
<br>
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.<br>
<br>
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.<br>
<br>
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.<br>
<br>
I’ll stop bloviating now.<br>
<br>
Sincerely,<br>
Eric Kolotyluk<br>
<br><p><br>
</p><p>On 2025-12-18 1:00 PM, David Alayachew wrote:</p>
<blockquote type="cite" cite="mid:CAA9v-_MxzoznSe6EFJ8P44RTQVm6SmVTiuVuQ-nN4xPPZppodw@mail.gmail.com">
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<div dir="auto">
<div>For 1, the javadoc absolutely does help you. Please read
for open.
<div dir="auto"><br>
</div>
<div dir="auto"><a href="https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/concurrent/StructuredTaskScope.html#open()" rel="noreferrer noreferrer" target="_blank" moz-do-not-send="true" class="moz-txt-link-freetext">https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/concurrent/StructuredTaskScope.html#open()</a></div>
<div dir="auto"><br>
</div>
<div dir="auto">As for verbose, can you go into more detail?
This is a traditional builder pattern addition, so it is
literally 1 static method call.</div>
<div dir="auto"><br>
</div>
<div dir="auto">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.</div>
<br>
<br>
<div class="gmail_quote">
<div dir="ltr" class="gmail_attr">On Thu, Dec 18, 2025,
3:46 PM Holo The Sage Wolf <<a href="mailto:holo3146@gmail.com" rel="noreferrer noreferrer" target="_blank" moz-do-not-send="true" class="moz-txt-link-freetext">holo3146@gmail.com</a>>
wrote:<br>
</div>
<blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<div dir="ltr">
<div>Hello Loom devs,</div>
<div>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.</div>
<div><br>
</div>
<div>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:</div>
<div><br>
</div>
<div>1. The default Joiner method can't receive timeout</div>
<div>Obviously that is wrong, but the API and JavaDoc
don't actually help you. Say you start with:</div>
<div> ```java</div>
<div>try (var scope =
StructuredTaskScope.open()) {</div>
<div> ...</div>
<div>}</div>
<div>```</div>
<div>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.</div>
<div>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:</div>
<div>
<div> ```java</div>
<div>try (var scope =
StructuredTaskScope.open(Joiner.awaitAllSuccessfulOrThrow(), (conf)
-> ...)) {</div>
<div> ...</div>
<div>}</div>
<div>```</div>
<div>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.</div>
<div><br>
</div>
<div>2. Changing only the timeout is "verbose".</div>
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.</div>
<div>I feel like adding a couple of overloads of the
open method that takes only an extra parameter of
duration will be convenient:</div>
<div>> StructuredTaskScope.open()</div>
<div>> StructuredTaskScope.open(Duration timeout)
<div>> StructuredTaskScope.open(Joiner joiner)</div>
<div>> StructuredTaskScope.open(Joiner joiner,
Duration timeout)
</div>
<div>> StructuredTaskScope.open(Joiner
joiner, Function<Configuration, Configuration>
configFunction)
</div>
<br>
</div>
</div>
</blockquote>
</div>
</div>
</div>
</blockquote>
</div>
</div></blockquote></div><br></div></body></html>