Java: parallel streams and CompletableFuture

Count items with a parallel stream and combine two asynchronous results.

Java
import java.util.concurrent.*;
import java.util.stream.*;

class Main {
  public static void main(String[] args) throws Exception {
    long evens = LongStream.rangeClosed(1, 1_000_000).parallel().filter(n -> n % 2 == 0).count();
    System.out.println("evens: " + evens);

    CompletableFuture<Integer> price = CompletableFuture.supplyAsync(() -> 40);
    CompletableFuture<Integer> tax = CompletableFuture.supplyAsync(() -> 2);
    int total = price.thenCombine(tax, Integer::sum).get();
    System.out.println("total: " + total);
  }
}
Output
evens: 500000
total: 42

Explanation

A parallel stream splits its data across the common fork/join pool and combines the partial results. It suits independent, stateless operations such as filtering and counting, where the answer does not depend on the order in which pieces finish.

CompletableFuture models a value that will be available later. supplyAsync starts work on another thread, and thenCombine joins two independent futures once both complete. This is the building block for asynchronous pipelines, such as calling two services at once.

Explore the dry run
Guided walkthroughReady
1import java.util.concurrent.*;
2import java.util.stream.*;
3
4class Main {
5 public static void main(String[] args) throws Exception {
6 long evens = LongStream.rangeClosed(1, 1_000_000).parallel().filter(n -> n % 2 == 0).count();
7 System.out.println("evens: " + evens);
8
9 CompletableFuture<Integer> price = CompletableFuture.supplyAsync(() -> 40);
10 CompletableFuture<Integer> tax = CompletableFuture.supplyAsync(() -> 2);
11 int total = price.thenCombine(tax, Integer::sum).get();
12 System.out.println("total: " + total);
13 }
14}

Follow the example

Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.

View the complete dry-run table
Prepared trace of the published program. Some rows group repeated iterations.
Step / lineWhat happensState after stepOutput so far
The range is split into parts, filtered on several threads, and the counts are added.evens = 500000
—
Counting is order-independent, so the result is always the same.evens = 500000
evens: 500000
Two independent computations start on pool threads.price and tax are running
evens: 500000
When both finish, their results are added; get waits for the answer.40 + 2 = 42
evens: 500000
Print the combined result.40 + 2 = 42
evens: 500000
total: 42

Cost and common mistakes

O(n) work for the stream, divided across the available cores; the asynchronous part is O(1).

  • Do not modify shared non-thread-safe collections from inside a parallel stream.
  • Parallel streams help only for large data and CPU-bound work. For small inputs they are often slower.
  • Blocking tasks in the common pool can starve other parallel work.

Try it yourself

How would you run the two futures on your own executor?

Reveal answer and reasoning

Pass the executor as a second argument: CompletableFuture.supplyAsync(() -> 40, executor).

Further reading