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.
On Wed, Sep 3, 2025 at 11:16 PM david Grajales <david.1993graja...@gmail.com> wrote: > I hope this message finds you well. Thank you for all the work you are > doing to improve the Java platform. > > 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 > StructuredTaskScope together with ArrayBlockingQueue as a “channel.” The > intention is to build groups of task pipelines step by step that can be > executed in parallel. > > Here’s a simplified snippet that captures the idea: > > void main(){ > var producerChannel = new ArrayBlockingQueue<HttpRequest>(100); > var consumerChannel = new ArrayBlockingQueue<HttpResponse<String>>(3); > var client = HttpClient.newBuilder().build(); > var request = HttpRequest.newBuilder() > .uri(URI.create("some url")) > .build(); > try(var routine = StructuredTaskScope.open()){ > for(var i = 0; i < 100; i++){ > routine.fork(() -> sendRequest(producerChannel, > consumerChannel, client)); > producerChannel.put(request); > } > > routine.fork(() -> processResponse(consumerChannel)); > routine.join(); > > } catch (InterruptedException e) { > throw new RuntimeException(e); > } > > } > > Void sendRequest(ArrayBlockingQueue<HttpRequest> producerChannel, > ArrayBlockingQueue<HttpResponse<String>> > consumerChannel, > HttpClient client) throws InterruptedException, > IOException { > var req = producerChannel.take(); > var res = client.send(req, HttpResponse.BodyHandlers.ofString()); > consumerChannel.put(res); > return null; > } > > Void processResponse(ArrayBlockingQueue<HttpResponse<String>> > consumerChannel) throws InterruptedException { > for (var i = 0; i < 100; i++){ > var value = consumerChannel.take().body(); > println(value); > } > var value = consumerChannel.take().body(); > println(value); > return null; > } > > One thing I noticed is that having to declare Void and return null > introduces a bit of noise. This ceremony is required because Runnable > 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. > > This makes the Void return type and return null feel redundant. I was > wondering: would it be worth considering the addition of a Runnable-like > functional interface that permits checked exceptions? > > Thank you for your time and consideration. I’d love to hear your thoughts. > > Best regards and always yours. > > David Grajales Cárdenas. >