<div dir="ltr"><div class="gmail_default" style="font-family:monospace"><a class="gmail_plusreply" id="m_757661458569578150m_-9097826531449512484m_7649916717109765593plusReplyChip-1" href="mailto:eric@kolotyluk.net" target="_blank">@Eric Kolotyluk</a> - mailing list etiquette dictates that a new topic should be a new thread. <a class="gmail_plusreply" id="m_757661458569578150m_-9097826531449512484m_7649916717109765593plusReplyChip-2" href="mailto:robaho@me.com" target="_blank">@Robert Engels</a> probably sent an email to you privately to avoid breaking etiquette.</div><div class="gmail_default" style="font-family:monospace"><br></div><div class="gmail_default" style="font-family:monospace">Since this conversation already started, just make a new thread, and then link back to this conversation via the archive post, then just continue the discussion where you left off.</div><div class="gmail_default" style="font-family:monospace"><br></div><div class="gmail_default" style="font-family:monospace">And to be clear, this discussion is absolutely worth having. You are making some decent points, and I have some to share. But for traceability sake, let's not hijack <a class="gmail_plusreply" id="m_757661458569578150m_-9097826531449512484m_7649916717109765593plusReplyChip-3" href="mailto:holo3146@gmail.com" target="_blank">@Holo The Sage Wolf</a>'s thread. Email me separately if you need help linking this conversation via archive post in the new thread.</div></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Thu, Dec 18, 2025 at 8:50 PM Robert Engels <<a href="mailto:robaho@me.com" target="_blank">robaho@me.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div dir="auto"><div dir="ltr"></div><div dir="ltr">Respectfully, no you’re arguing that because structured concurrency is a new addition this is an opportunity to explore using values as errors. That would be a poor choice. Java developers have successfully written billions of lines of code using exceptions. It’s not hard or error prone if you take the time to understand it - and it’s far better than errors as values like Rust/Go/C has. </div><div dir="ltr"><br><blockquote type="cite">On Dec 18, 2025, at 6:46 PM, Eric Kolotyluk <<a href="mailto:eric@kolotyluk.net" target="_blank">eric@kolotyluk.net</a>> wrote:<br><br></blockquote></div><blockquote type="cite"><div dir="ltr">

  
    
  
  
    Respectfully, I think we’re talking past each other a bit.<br>
    <br>
    Calling Rust’s error handling “horrible” is a subjective judgment
    about trade-offs, not an objective flaw. Rust’s Result<T, E>
    is deliberately value-oriented and explicit; Java’s exception model
    is deliberately stack-oriented and implicit. Each optimizes for
    different things, and each has real costs.<br>
    <br>
    My appreciation of Java’s evolution is that it has consistently
    expanded the set of available tools, rather than insisting on a
    single paradigm. Generics, lambdas, streams, records, sealed types,
    Optional, and now Loom itself all reflect that trajectory. They
    didn’t replace older mechanisms; they complemented them.<br>
    <br>
    There has been sustained criticism in the Java community of both
    null and over-reliance on exceptions, particularly where failure is
    expected rather than exceptional. I’m not here to relitigate either
    debate, nor to argue that exceptions should go away. My point is
    simply that other options exist, and Java has historically been at
    its best when APIs acknowledge and support them.<br>
    <br>
    In that light, my concern with StructuredTaskScope.join() is not
    that it uses exceptions at all, but that it offers only an
    exception-based outcome model, with null representing success. That
    feels like a missed opportunity in an otherwise forward-looking API.<br>
    <br>
    I’m advocating for additional, not replacement, abstractions—ones
    that allow structured concurrency outcomes to be expressed
    explicitly when appropriate, while leaving exceptions fully
    available for genuinely exceptional conditions.<br>
    <br>
    Respectfully,<br>
    Eric Kolotyluk
    <p><br>
    </p>
    <div>On 2025-12-18 1:34 PM, Robert Engels
      wrote:<br>
    </div>
    <blockquote type="cite">
      
      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
              <a href="mailto:eric@kolotyluk.net" target="_blank"><eric@kolotyluk.net></a> wrote:</div>
            <br>
            <div>
              
              <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">
                  
                  <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">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">holo3146@gmail.com</a>>
                          wrote:<br>
                        </div>
                        <blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);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>
    </blockquote>
  

</div></blockquote></div></blockquote></div>