Custom java.util.stream.Collector implementations for parallel processing with CompletableFutures and Virtual Threads. Zero dependencies.
Standard parallel streams use the shared ForkJoinPool — terrible for I/O-bound work. Parallel Collectors fix that.
Defaults to Virtual Threads on JDK 21+. Lightweight concurrency without managing thread pools.
Returns CompletableFuture — the calling thread never blocks. Compose, chain, and add timeouts freely.
Custom executors, parallelism limits, and batching. Full control over how work gets distributed.
Group fine-grained tasks into batches to minimize contention. Benchmarked up to 162x speedup.
Nothing but the JDK. No transitive dependency trees, no version conflicts, no bloat.
If one operation throws, remaining tasks are cancelled and in-flight tasks are interrupted where possible.
Familiar Stream API patterns with parallel execution backed by CompletableFuture.
// Parallel collection with Virtual Threads (default)
CompletableFuture<List<String>> result = urls.stream()
.collect(parallel(url -> fetchData(url), toList()));
// With parallelism limit, batching, custom executor
CompletableFuture<List<String>> result = urls.stream()
.collect(parallel(url -> fetchData(url), c -> c
.parallelism(64)
.batching()
, toList()));
// Stream results as they complete
Stream<String> result = urls.stream()
.collect(parallelToStream(url -> fetchData(url)));
// Or preserve input order
Stream<String> ordered = urls.stream()
.collect(parallelToStream(url -> fetchData(url), c -> c.ordered()));
// Non-blocking with timeout and async callback
urls.stream()
.collect(parallel(url -> fetchData(url), toList()))
.orTimeout(5, SECONDS)
.thenAccept(System.out::println);
Static factory methods on ParallelCollectors — follows java.util.stream.Collectors conventions.
Parallel mapping with a downstream collector for reduction. Returns a future of the collected result.
Parallel mapping without reduction. Returns a future wrapping a stream of mapped results.
Returns a stream that emits results in completion order. Use c → c.ordered() to preserve input order.
Classifies elements into groups and streams grouped results as they complete.
Classifies elements into groups and processes each group in parallel. Results keyed by group.
Groups elements in parallel and reduces grouped results with a downstream collector.
// The Configurer API — fluent configuration for all collectors
urls.stream().collect(parallel(url -> fetch(url), c -> c
.executor(myExecutor) // custom Executor (default: Virtual Threads)
.parallelism(32) // max concurrent tasks
.batching() // group work into batches
, toList()));
// Streaming collectors also support ordered emission
urls.stream().collect(parallelToStream(url -> fetch(url), c -> c
.ordered() // preserve input order
.parallelism(32)
));
<dependency>
<groupId>com.pivovarit</groupId>
<artifactId>parallel-collectors</artifactId>
<version>4.0.0</version>
</dependency>
implementation 'com.pivovarit:parallel-collectors:4.0.0'
<dependency>
<groupId>com.pivovarit</groupId>
<artifactId>parallel-collectors</artifactId>
<version>2.6.1</version>
</dependency>
implementation 'com.pivovarit:parallel-collectors:2.6.1'
3.x uses Virtual Threads by default. Version 2.x supports JDK 8+ with platform threads.