Virtual threads are free

On JVM 21+, each session runs on its own virtual thread. Blocking on I/O yields the carrier. There's no penalty for a blocking API — and the code is much simpler.

Advertisement

Reactive is now overhead

Adding CompletableFuture everywhere makes the API harder to compose, especially with try-with-resources and stream operations. Virtual threads removed the need.

Advertisement

Streaming is the exception

public Stream generateStream(GenerateRequest req);
// Stream is the natural fit for chunked responses — no need for Flow/Publisher.