<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  </head>
  <body>
    Upon reviewing the current documentation, I see that I misstated an
    earlier point. StructuredTaskScope.join() returns a result of type R
    (as produced by the configured Joiner), rather than returning null
    unconditionally on success.<br>
    <br>
    That said, the outcome contract remains: join() either returns a
    value or throws. After waiting for completion or cancellation, the
    scope invokes Joiner.result(); if that method throws, join() throws
    FailedException with the underlying exception as the cause (with
    timeouts and cancellation also surfaced as exceptions).<br>
    <br>
    So while Joiners make join() result-producing and more configurable,
    the failure channel is still exception-based. From that perspective,
    I can still see value in a functional, value-oriented result
    type—where success and failure are both represented explicitly as
    values—coexisting alongside exceptions, rather than routing expected
    failure exclusively through throws.<br>
    <br>
    Joiners improve policy flexibility, but they don’t quite address
    that particular concern.<br>
    <br>
    Respectfully,<br>
    <p>Eric Kolotyluk</p>
    <p><br>
    </p>
    <div class="moz-cite-prefix">On 2025-12-18 4:46 PM, Eric Kolotyluk
      wrote:<br>
    </div>
    <blockquote type="cite"
      cite="mid:b309d805-d90b-4657-a5f7-2031ce26d879@kolotyluk.net">
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
      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 class="moz-cite-prefix">On 2025-12-18 1:34 PM, Robert Engels
        wrote:<br>
      </div>
      <blockquote type="cite"
        cite="mid:8C2482BF-515C-4A89-8E68-197399E89452@me.com">
        <meta http-equiv="content-type"
          content="text/html; charset=UTF-8">
        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
                  class="moz-txt-link-rfc2396E"
                  href="mailto:eric@kolotyluk.net"
                  moz-do-not-send="true"><eric@kolotyluk.net></a>
                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>
      </blockquote>
    </blockquote>
  </body>
</html>