<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>