<div dir="ltr"><div class="gmail_default" style="font-family:monospace">Full agreement on the suggestion to have the Runnable-like interface that throws. It's common enough that it would be very useful for me. Void was a workaround for a bad situation, but it doesn't mean we should have to be stuck with it for a new API.</div></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Wed, Sep 3, 2025 at 11:16 PM david Grajales <<a href="mailto:david.1993grajales@gmail.com" target="_blank">david.1993grajales@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"><p>I hope this message finds you well. Thank you for all the work you are doing to improve the Java platform.</p>
<p>I wanted to share some thoughts from a small experiment I’ve been running with structured concurrency. The idea was to mimic (to some extent) Go’s goroutines and channels, modeling an N:M producer–consumer fan-out using <code>StructuredTaskScope</code> together with <code>ArrayBlockingQueue</code> as a “channel.” The intention is to build groups of task pipelines step by step that can be executed in parallel.</p>
<p>Here’s a simplified snippet that captures the idea:</p><div><br></div><div>void main(){<br>    var producerChannel = new ArrayBlockingQueue<HttpRequest>(100);<br>    var consumerChannel = new ArrayBlockingQueue<HttpResponse<String>>(3);<br>    var client = HttpClient.newBuilder().build();<br>    var request = HttpRequest.newBuilder()<br>        .uri(URI.create("some url"))<br>        .build();<br>    try(var routine = StructuredTaskScope.open()){<br>        for(var i = 0; i < 100; i++){<br>            routine.fork(() -> sendRequest(producerChannel, consumerChannel, client));<br>            producerChannel.put(request);<br>        }<br><br>        routine.fork(() -> processResponse(consumerChannel));<br>        routine.join();<br><br>    } catch (InterruptedException e) {<br>        throw new RuntimeException(e);<br>    }<br><br>}<br><br>Void sendRequest(ArrayBlockingQueue<HttpRequest> producerChannel,<br>                          ArrayBlockingQueue<HttpResponse<String>> consumerChannel,<br>                          HttpClient client) throws InterruptedException, IOException {<br>    var req = producerChannel.take();<br>    var res = client.send(req, HttpResponse.BodyHandlers.ofString());<br>    consumerChannel.put(res);<br>    return null;<br>}<br><br>Void processResponse(ArrayBlockingQueue<HttpResponse<String>> consumerChannel) throws InterruptedException {<br>    for (var i = 0; i < 100; i++){<br>        var value = consumerChannel.take().body();<br>        println(value);<br>    }<br>    var value = consumerChannel.take().body();<br>    println(value);<br>    return null;<br>}</div><div><br></div><div><p>One thing I noticed is that having to declare <code>Void</code> and return <code>null</code> introduces a bit of noise. This ceremony is required because <code>Runnable</code> doesn’t allow checked exceptions in its contract (and never will, for backwards compatibility). Yet, in practice, many real-world tasks don’t return values directly — instead, they write to or consume from buffers.</p>
<p>This makes the <code>Void</code> return type and <code>return null</code> feel redundant. I was wondering: would it be worth considering the addition of a <code>Runnable</code>-like functional interface that permits checked exceptions?</p>
<p>Thank you for your time and consideration. I’d love to hear your thoughts.</p>
<p>Best regards and always yours.</p><p>David Grajales Cárdenas.</p></div></div>
</blockquote></div>