DevNotes
Support
Java 8 & Streams · Chapter 17

Parallel Streams and Performance

How parallelStream() works — Fork/Join, the common pool, ordering, thread safety, associativity, and when parallel makes things slower.

◷ 7 min read§ 20 sections? 6 interview questions{ } 19 code examples

#Introduction

One of the most misunderstood Java 8 topics. Many developers think:

In many real-world applications, parallelStream() can make your application slower. Understanding when to use it and when to avoid it is a common interview topic for 5–10 years of experience.

By the end of this chapter you'll master sequential vs parallel streams, how parallel streams work internally, the Fork/Join framework, splitting and merging, the common pool, thread safety, ordering, performance and when not to use parallel streams.

#Sequential Stream

Java
List<Integer> numbers = List.of(1, 2, 3, 4, 5);

numbers.stream()
       .forEach(System.out::println);
Thread-1
1
2
3
4
5
Everything happens in one thread

#Parallel Stream

Java
numbers.parallelStream()
       .forEach(System.out::println);
Thread-1
13
Thread-2
25
Thread-3
4
Multiple threads execute simultaneously

#How Does It Work?

Suppose 1 2 3 4 5 6 7 8.

1 2 3 4 5 6 7 8
Thread A: 1 2 · Thread B: 3 4 · Thread C: 5 6 · Thread D: 7 8
Result

This is called the divide-and-conquer approach.

#Fork/Join Framework

Parallel streams use Java's Fork/Join Framework internally (introduced in Java 7).

Big task
Split (fork)
Worker threads
Merge (join)
Final result

Example: Sum 1…1,000,000

Instead of one thread doing 1 million additions, Java does:

Thread 1
1..250000
Thread 2
250001..500000
Thread 3
500001..750000
Thread 4
750001..1000000

…and then combines the four partial sums.

#Common ForkJoinPool

Parallel streams use ForkJoinPool.commonPool() by default. The pool size is usually CPU cores − 1 — e.g. an 8-core CPU gives 7 worker threads. The calling thread (often main) also participates in the work. Interview favourite.

Checking Thread Names

Java
IntStream.rangeClosed(1, 10)
         .parallel()
         .forEach(i -> System.out.println(
                 Thread.currentThread().getName() + " : " + i));
Output
ForkJoinPool.commonPool-worker-1 : 7
ForkJoinPool.commonPool-worker-3 : 3
main : 6
ForkJoinPool.commonPool-worker-2 : 9
...

Now you can actually see parallel execution.

#parallel() vs parallelStream()

Both are equivalent:

Java
list.parallelStream()

// equals

list.stream()
    .parallel()

Both create a parallel stream.

#When Parallel Streams Shine

CPU-intensive work — image processing, encryption, compression, mathematical calculations, machine-learning calculations.

Java
numbers.parallelStream()
       .map(this::expensiveCalculation)
       .toList();

Each calculation is independent. Perfect candidate.

#When NOT to Use Parallel Streams

Database Calls

Java
// ❌ Bad
users.parallelStream()
     .map(repository::findOrders)

Why? Multiple threads now compete for database connections, transactions and the connection pool. Often slower.

REST API Calls

Java
// ❌ Bad
users.parallelStream()
     .map(api::fetchUser)

Use CompletableFuture, reactive programming or async APIs instead.

Small Collections

Java
// ❌ Bad
List.of(1, 2, 3, 4)
    .parallelStream()

Splitting work across threads costs more than the work itself.

#Thread Safety

Bad Example

Java
List<Integer> result = new ArrayList<>();

numbers.parallelStream()
       .forEach(result::add);

Problem: multiple threads modify an ArrayList. Result: missing values, corrupted data, exceptions (e.g. ArrayIndexOutOfBoundsException).

Correct

Java
List<Integer> result = numbers.parallelStream()
        .collect(Collectors.toList());

Collectors handle parallel accumulation safely — each thread fills its own container and the combiner merges them.

#Ordering

Java
numbers.stream()
       .forEach(System.out::println);           // 1 2 3 4 5

numbers.parallelStream()
       .forEach(System.out::println);           // possible: 3 1 5 2 4

Order is not guaranteed. Need order? Use:

Java
numbers.parallelStream()
       .forEachOrdered(System.out::println);    // 1 2 3 4 5

But forEachOrdered() reduces some of the benefits of parallel execution because it must preserve encounter order.

#Reduction in Parallel

Java
// ✅ Good — addition is associative. Safe.
int total = IntStream.rangeClosed(1, 100)
        .parallel()
        .sum();

// ❌ Bad — subtraction depends on order
.parallel()
.reduce((a, b) -> a - b)

A different execution order may produce different results.

#Stateless vs Stateful Operations

Stateless — good for parallelStateful — require coordination
map()sorted()
filter()distinct()
mapToInt()limit()
Each element is independentOften reduce the benefits of parallel processing

#Real Spring Boot Examples

Java
// ✅ Good — image compression is CPU-intensive and independent
images.parallelStream()
      .map(ImageService::compress)
      .toList();

// ❌ Bad — database contention, transaction overhead, connection pool limits
employees.parallelStream()
         .forEach(employeeRepository::save);

// ✅ Good — checksum calculations are CPU-bound
files.parallelStream()
     .map(FileProcessor::checksum)
     .toList();

#Performance Rule

Large data
CPU-intensive
Independent tasks
Parallel is beneficial when

Not beneficial when there is small data, I/O, or shared state.

#Parallel Stream Pipeline

Java
employees.parallelStream()
         .filter(Employee::isActive)
         .map(Employee::getSalary)
         .map(this::calculateTax)
         .sorted()
         .limit(100)
         .toList();

Think about the stages:

StageParallel behaviour
filter()Parallel-friendly
map()Parallel-friendly
calculateTax()If CPU-intensive, parallel helps
sorted()Requires coordination
limit()May reduce efficiency (ordered streams)

Not every stage benefits equally.

#Interview Questions

Q1Difference between stream() and parallelStream()?
stream()parallelStream()
Single threadMultiple threads
OrderedOrder not guaranteed with forEach()
Less overheadThread-management overhead
Q2Which thread pool is used?

ForkJoinPool.commonPool().

Q3When should parallel streams be avoided?

Database operations, REST API calls, small collections, and shared mutable state.

Q4Difference between forEach() and forEachOrdered()?
  • forEach() → faster, no ordering guarantee.
  • forEachOrdered() → preserves encounter order.
Q5Why can ArrayList fail in parallel streams?

Because it's not thread-safe.

Q6What kinds of operations work best?

Operations that are independent, CPU-intensive, and associative (for reductions).

#Practice Problems

  1. Print thread names using a parallel stream.
  2. Calculate the sum of numbers from 1 to 1,000,000 using a sequential stream and a parallel stream. Observe the difference.
  3. Compress a list of images in parallel.
  4. Generate checksums for 1000 files using parallel streams.
  5. Use forEach() and forEachOrdered() on the same dataset and compare the output.
  6. Why is the following code unsafe, and how would you fix it?
Java
List<Integer> list = new ArrayList<>();

IntStream.rangeClosed(1, 1000)
         .parallel()
         .forEach(list::add);
  1. Identify whether each scenario should use sequential or parallel streams:
  2. Summing 10 integers
  3. Processing 5 million images
  4. Saving 1000 records to a database
  5. Calculating SHA-256 hashes for 1 million files
  6. Calling an external payment API

5+ Years Interview Challenge

Each employee has a large PDF report that must be encrypted. Return a list of encrypted reports.

Java
List<EncryptedReport> reports = employees.parallelStream()
        .map(employee -> encryptionService.encrypt(employee.getReport()))
        .toList();

Why is this a good candidate?

  • Each encryption is independent.
  • Encryption is CPU-intensive.
  • No shared mutable state.
  • No database or network calls in the stream.

#Senior Java Tips

1. Don't use parallelStream() by default

Many developers assume stream() → parallelStream() means "faster". This is one of the biggest misconceptions in Java. Always measure before optimizing.

2. Avoid I/O inside parallel streams

Database calls, REST calls and file writes are usually better handled with dedicated asynchronous APIs like CompletableFuture or reactive frameworks rather than parallelStream(). Blocking I/O in the common pool also starves every other parallel stream in the JVM.

3. Be careful with shared mutable state

This is unsafe:

Java
.parallelStream()
.forEach(sharedList::add);

Prefer immutable transformations and collectors:

Java
.parallelStream()
.map(...)
.collect(Collectors.toList());

#Chapter Summary

  • Parallel streams split work with Fork/Join on ForkJoinPool.commonPool()
  • parallel() ≡ parallelStream()
  • Good for large, CPU-bound, independent work; bad for I/O, small data, shared state
  • forEach() vs forEachOrdered()
  • Reductions must be associative
  • Stateless vs stateful operations