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.

Reply via email to