<div dir="ltr"><div>Hello Eric,</div><div><br></div><div>Here is a simple implementation of "exception as values" structured concurrency *using the current architecture* as a Proof of Concept, this is a working example in Java 25 with the flag --enable-preview.</div><div><br></div><div>Couple of notes: </div><div>1. I used the most on the nose naive implementation of Result<Value, Error> type just as a Proof of Concept, but this can be replaced with a more sophisticated type.</div><div>2. I didn't implement the timeout feature as "exception as value", meaning using this PoC the only way to use timeout is via the ConfigFunc which *will* throw an exception on timeout. </div><div>    - It is possible and relatively simply to implement timeout inside the Joiner and use that instead of the ConfigFunc feature of timeout and thus bypass this point</div><div>3. This is specifically a "on all success return stream of results" implementation, this implementation can easily adapted to all other usecases via minimal changes</div><div><br></div><div>Minus the above caveats, this implementation is production ready. </div><div>The only actual downsides I can think of about this implementation are:</div><div>1. Even though InterruptedException should never be thrown, you still have to catch it</div><div>2. There is no way to prevent users from setting timeout via ConfFunc even if `AllSuccessfulResult` were to implement timeout mechanism</div><div><br></div><div><div style="background-color:rgb(30,31,34);color:rgb(188,190,196)"><pre style="font-family:"JetBrains Mono",monospace;font-size:9.8pt"><span style="color:rgb(179,174,96)">@SuppressWarnings</span>(<span style="color:rgb(106,171,115)">"preview"</span>)<br><span style="color:rgb(207,142,109)">void </span><span style="color:rgb(86,168,245)">main</span>() {<br>    <span style="color:rgb(207,142,109)">try </span>(<span style="color:rgb(207,142,109)">var </span>scope = StructuredTaskScope.<span style="font-style:italic">open</span>(<span style="color:rgb(207,142,109)">new </span>AllSuccessfulResult<String>())) {<br>         scope.fork(() -> <span style="color:rgb(106,171,115)">"Holo"</span>);<br>         scope.fork(() -> <span style="color:rgb(106,171,115)">"Test"</span>);<br>         <span style="color:rgb(207,142,109)">var </span>x = scope.join(); <span style="color:rgb(122,126,133)">// x=(stream of ("Holo", "Test"), null), the order of the stream is not guarantee</span><br><span style="color:rgb(122,126,133)"><br></span><span style="color:rgb(122,126,133)">        // scope.fork(() -> "Holo");<br></span><span style="color:rgb(122,126,133)">        // scope.fork(() -> "Test");<br></span><span style="color:rgb(122,126,133)">        // scope.fork(() -> {<br></span><span style="color:rgb(122,126,133)">        //     throw new IllegalStateException();<br></span><span style="color:rgb(122,126,133)">        // });<br></span><span style="color:rgb(122,126,133)">        // var x = scope.join(); // x=(null, illegalStateException)<br></span><span style="color:rgb(122,126,133)">    </span>} <span style="color:rgb(207,142,109)">catch </span>(InterruptedException | StructuredTaskScope.TimeoutException e) {<br>        <span style="color:rgb(207,142,109)">throw new </span>RuntimeException(e); <span style="color:rgb(122,126,133)">// only teachable on timeout *when the timeout is configured through StructuredTaskScope.open*<br></span><span style="color:rgb(122,126,133)">        // To make it so timeout doesn't get into here, add into the `AllSuccessfulResult` type built in mechanism of timeout<br></span><span style="color:rgb(122,126,133)">        //      implementing timeout inside the Joiner is not hard, but I omitted it from the Proof of Concept<br></span><span style="color:rgb(122,126,133)">    </span>}<br>}<br><br><span style="color:rgb(207,142,109)">record </span>Result<<span style="color:rgb(22,186,172)">V</span>, <span style="color:rgb(22,186,172)">R </span><span style="color:rgb(207,142,109)">extends </span>Throwable>(<span style="color:rgb(22,186,172)">V </span><span style="color:rgb(199,125,187)">value</span>, <span style="color:rgb(22,186,172)">R </span><span style="color:rgb(199,125,187)">exception</span>) {}<br><br><span style="color:rgb(179,174,96)">@SuppressWarnings</span>(<span style="color:rgb(106,171,115)">"preview"</span>)<br><span style="color:rgb(207,142,109)">static final class </span>AllSuccessfulResult<<span style="color:rgb(22,186,172)">T</span>> <span style="color:rgb(207,142,109)">implements </span>StructuredTaskScope.Joiner<<span style="color:rgb(22,186,172)">T</span>, Result<Stream<<span style="color:rgb(22,186,172)">T</span>>, Throwable>> {<br>    <span style="color:rgb(207,142,109)">private static final </span>MethodHandles.Lookup <span style="color:rgb(199,125,187);font-style:italic">lookup </span>= MethodHandles.<span style="font-style:italic">lookup</span>();<br>    <span style="color:rgb(207,142,109)">private static final </span>VarHandle <span style="color:rgb(199,125,187);font-style:italic">FIRST_EXCEPTION</span>;<br><br>    <span style="color:rgb(207,142,109)">static </span>{<br>        <span style="color:rgb(207,142,109)">try </span>{<br>            <span style="color:rgb(199,125,187);font-style:italic">FIRST_EXCEPTION </span>= <span style="color:rgb(199,125,187);font-style:italic">lookup</span>.findVarHandle(<span style="color:rgb(199,125,187);font-style:italic">lookup</span>.lookupClass(), <span style="color:rgb(106,171,115)">"firstException"</span>, Throwable.<span style="color:rgb(207,142,109)">class</span>);<br>        } <span style="color:rgb(207,142,109)">catch </span>(NoSuchFieldException | IllegalAccessException e) {<br>            <span style="color:rgb(207,142,109)">throw new </span>RuntimeException(e); <span style="color:rgb(122,126,133)">// shouldn't be possible to reach<br></span><span style="color:rgb(122,126,133)">        </span>}<br>    }<br><br>    <span style="color:rgb(122,126,133)">// list of forked subtasks, only accessed by owner thread<br></span><span style="color:rgb(122,126,133)">    </span><span style="color:rgb(207,142,109)">private final </span>List<StructuredTaskScope.Subtask<<span style="color:rgb(22,186,172)">T</span>>> <span style="color:rgb(199,125,187)">subtasks </span>= <span style="color:rgb(207,142,109)">new </span>ArrayList<>();<br><br>    <span style="color:rgb(207,142,109)">private volatile </span>Throwable <span style="color:rgb(199,125,187)">firstException</span>;<br><br>    <span style="color:rgb(179,174,96)">@Override<br></span><span style="color:rgb(179,174,96)">    </span><span style="color:rgb(207,142,109)">public boolean </span><span style="color:rgb(86,168,245)">onFork</span>(StructuredTaskScope.Subtask<? <span style="color:rgb(207,142,109)">extends </span><span style="color:rgb(22,186,172)">T</span>> subtask) {<br>        <span style="color:rgb(207,142,109)">if </span>(subtask.state() != StructuredTaskScope.Subtask.State.<span style="color:rgb(199,125,187);font-style:italic">UNAVAILABLE</span>) { <span style="color:rgb(122,126,133)">// after `join` you can't use onFork<br></span><span style="color:rgb(122,126,133)">            </span><span style="color:rgb(207,142,109)">throw new </span>IllegalArgumentException(<span style="color:rgb(106,171,115)">"Subtask not in UNAVAILABLE state"</span>);<br>        }<br>        <span style="color:rgb(179,174,96)">@SuppressWarnings</span>(<span style="color:rgb(106,171,115)">"unchecked"</span>)<br>        <span style="color:rgb(207,142,109)">var </span>s = (StructuredTaskScope.Subtask<<span style="color:rgb(22,186,172)">T</span>>) subtask;<br>        <span style="color:rgb(199,125,187)">subtasks</span>.add(s);<br>        <span style="color:rgb(207,142,109)">return false</span>;<br>    }<br><br>    <span style="color:rgb(179,174,96)">@Override<br></span><span style="color:rgb(179,174,96)">    </span><span style="color:rgb(207,142,109)">public boolean </span><span style="color:rgb(86,168,245)">onComplete</span>(StructuredTaskScope.Subtask<? <span style="color:rgb(207,142,109)">extends </span><span style="color:rgb(22,186,172)">T</span>> subtask) {<br>        StructuredTaskScope.Subtask.State state = subtask.state();<br>        <span style="color:rgb(207,142,109)">if </span>(state == StructuredTaskScope.Subtask.State.<span style="color:rgb(199,125,187);font-style:italic">UNAVAILABLE</span>) {<br>            <span style="color:rgb(207,142,109)">throw new </span>IllegalArgumentException(<span style="color:rgb(106,171,115)">"Subtask has not completed"</span>); <span style="color:rgb(122,126,133)">// this shouldn't be reachable I think, but just in case<br></span><span style="color:rgb(122,126,133)">        </span>}<br><br>        <span style="color:rgb(207,142,109)">return </span>(state == StructuredTaskScope.Subtask.State.<span style="color:rgb(199,125,187);font-style:italic">FAILED</span>)<br>                && (<span style="color:rgb(199,125,187)">firstException </span>== <span style="color:rgb(207,142,109)">null</span>)<br>                && <span style="color:rgb(199,125,187);font-style:italic">FIRST_EXCEPTION</span>.compareAndSet(<span style="color:rgb(207,142,109)">this</span>, <span style="color:rgb(207,142,109)">null</span>, subtask.exception());<br>    }<br><br>    <span style="color:rgb(179,174,96)">@Override<br></span><span style="color:rgb(179,174,96)">    </span><span style="color:rgb(207,142,109)">public </span>Result<Stream<<span style="color:rgb(22,186,172)">T</span>>, Throwable> <span style="color:rgb(86,168,245)">result</span>() {<br>        <span style="color:rgb(207,142,109)">if </span>(<span style="color:rgb(199,125,187)">firstException </span>!= <span style="color:rgb(207,142,109)">null</span>) {<br>            <span style="color:rgb(207,142,109)">return new </span>Result<>(<span style="color:rgb(207,142,109)">null</span>, <span style="color:rgb(199,125,187)">firstException</span>);<br>        }<br>        <span style="color:rgb(207,142,109)">var </span>results = <span style="color:rgb(199,125,187)">subtasks</span>.stream()<br>                .filter(t -> t.state() == StructuredTaskScope.Subtask.State.<span style="color:rgb(199,125,187);font-style:italic">SUCCESS</span>)<br>                .map(StructuredTaskScope.Subtask::get);<br>        <span style="color:rgb(207,142,109)">return new </span>Result<>(results, <span style="color:rgb(207,142,109)">null</span>);<br>    }<br>}<br></pre></div><br></div></div><br><div class="gmail_quote gmail_quote_container"><div dir="ltr" class="gmail_attr">On Fri, Dec 19, 2025 at 11:10 AM Davor Hrg <<a href="mailto:hrgdavor@gmail.com">hrgdavor@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">I have also been experimenting with exceptions as values.<div><br></div><div>It served me well for some initial experiments with wrapping HTTP fetch-like utilities.</div><div><br></div><div>One issue came up: It was fine while I was not interested in what exception happens,</div><div>but as soon as I wanted to check multiple exception types I had to resort to Throwable.</div><div><br></div><div>My exploration is still superficial, but I have not found a way to define exception as value</div><div>where it can define  what "throws IoException,ValidationError" can. we would maybe need union types. ...</div><div>and I am also afraid what happy when we start mixing the two concepts all over the codebases.</div><div><br></div><div>This exploration of errors as types also made me appreciate Exceptions bit more.</div><div><br></div><div><br></div><div><br></div><div><br></div><div><br></div><div><br></div></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Fri, Dec 19, 2025 at 7:35 AM Eric Kolotyluk <<a href="mailto:eric@kolotyluk.net" target="_blank">eric@kolotyluk.net</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"><u></u>

  

    
  
  <div>
    Hi all,<br>
    <br>
    I’m starting a new thread to continue a discussion that emerged
    elsewhere, per mailing list etiquette, and to give the topic a clean
    and traceable home.<br>
    <br>
    My interest here isn’t reactive to any one exchange. I’ve been
    experimenting with Loom since its early iterations, and over time it
    has sharpened a concern I already had: whether Java’s traditional
    exception model remains the right default abstraction in a world of
    structured concurrency, virtual threads, and large-scale
    composition.<br>
    <br>
    To be clear, this is not a claim that “exceptions are broken” or
    that Java should abandon them. Java’s exception system has supported
    billions of lines of successful code, and I’ve used it productively
    for decades. Rather, Loom makes certain trade-offs more visible —
    particularly around control flow, cancellation, failure propagation,
    and reasoning about lifetimes — that were easier to ignore in a
    purely thread-per-task world.<br>
    <br>
    The core questions I’m interested in exploring are along these
    lines:<br>
    <ul>
      <li>How do unchecked exceptions interact with structured
        concurrency’s goal of making lifetimes and failure scopes
        explicit?</li>
      <li>Do exceptions remain the best abstraction for expected failure
        in highly concurrent, compositional code?</li>
      <li>Are there patterns (or emerging idioms) that Loom encourages
        which mitigate long-standing concerns with exceptions — or does
        Loom expose new ones?</li>
      <li>More broadly, should Java be thinking in terms of additional
        failure-handling tools rather than a single dominant model?</li>
    </ul>
    I’m not advocating a specific alternative here — just inviting a
    technical discussion about whether Loom changes how we should think
    about error handling, and if so, how.<br>
    <br>
    That said, exposure to other ecosystems (e.g., Scala, Kotlin, and
    more recently Rust) has broadened how I think about failure
    modeling. One thing I’ve consistently appreciated about Java is that
    it tends to integrate external ideas deliberately, rather than
    reflexively rejecting them or adopting them wholesale. Loom itself
    is a good example of that approach.<br>
    <br>
    I’m interested in whether error handling deserves a similar
    re-examination in light of Loom’s goals.<br>
    <br>
    Looking forward to the discussion.<br>
    <br>
    Cheers,<br>
    Eric
  </div>

</blockquote></div>
</blockquote></div><div><br clear="all"></div><br><span class="gmail_signature_prefix">-- </span><br><div dir="ltr" class="gmail_signature"><div dir="ltr">Holo The Wise Wolf Of Yoitsu</div></div>