Files
java-design-patterns/fork-join
SandhyaandGitHub 5698dea49b feat: Add Fork/Join design pattern (#3227) (#3550)
* Add fork-join pattern implementation

* Add input validation for start > end in SumTask

Signed-off-by: SandhyaDevadiga <sandhyadevadiga8197@gmail.com>

* Add fork-join module to parent pom.xml

* Add missing assertThrows import in SumTaskTest

* Format fork-join code with Spotless

* Add AppTest to satisfy coverage requirements

Signed-off-by: SandhyaDevadiga <sandhyadevadiga8197@gmail.com>

---------

Signed-off-by: SandhyaDevadiga <sandhyadevadiga8197@gmail.com>
2026-08-25 07:39:07 +03:00
..

title, shortTitle, description, category, language, tag
title shortTitle description category language tag
Fork/Join Pattern in Java: Parallel Divide-and-Conquer Processing Fork/Join Learn the Fork/Join design pattern in Java with real-world examples, class diagrams, and code samples. Understand how to split large tasks into parallel subtasks for improved performance. Concurrency en
Performance
Scalability
Concurrency

Also known as

  • Divide and Conquer Parallelism
  • Work-Stealing Parallelism

Intent of Fork/Join Design Pattern

The Fork/Join pattern recursively splits a large task into independent subtasks (fork), processes them in parallel across multiple threads, and combines their results (join) to produce a final outcome. It maximizes CPU utilization for computationally intensive problems.

Detailed Explanation of Fork/Join Pattern with Real-World Examples

Real-world example

Imagine a large warehouse that needs to count all its inventory items across 100 aisles. Instead of one person counting every aisle sequentially, the manager divides the warehouse into sections and assigns a team of workers to count each section simultaneously. Once every section is counted, the manager collects all partial counts and sums them into the total inventory. This is the Fork/Join pattern: split the work, do it in parallel, merge the results.

In plain words

Fork/Join splits a big problem into smaller pieces, solves each piece in parallel on separate threads, then combines all results back together.

Programmatic Example of Fork/Join Pattern in Java

We demonstrate the pattern by computing the sum of a large array in parallel using Java's built-in ForkJoinPool and RecursiveTask.

The SumTask is a recursive task that splits the array when it's too large:

public class SumTask extends RecursiveTask<Long> {

  private static final int THRESHOLD = 1000;
  private final long[] numbers;
  private final int start;
  private final int end;

  @Override
  protected Long compute() {
    int length = end - start;

    if (length <= THRESHOLD) {
      // Base case: sum directly
      long sum = 0;
      for (int i = start; i < end; i++) {
        sum += numbers[i];
      }
      return sum;
    }

    // Fork: split into two halves
    int mid = start + length / 2;
    SumTask leftTask = new SumTask(numbers, start, mid);
    SumTask rightTask = new SumTask(numbers, mid, end);

    leftTask.fork();                    // run left half asynchronously
    long rightResult = rightTask.compute();  // compute right half here
    long leftResult = leftTask.join();       // wait for left half

    // Join: combine results
    return leftResult + rightResult;
  }
}

The ForkJoinSumCalculator provides a clean API:

public class ForkJoinSumCalculator {

  private final ForkJoinPool pool;

  public ForkJoinSumCalculator() {
    this.pool = ForkJoinPool.commonPool();
  }

  public long calculateSum(long[] numbers) {
    SumTask task = new SumTask(numbers, 0, numbers.length);
    return pool.invoke(task);
  }
}

Running the example in App:

long[] numbers = LongStream.rangeClosed(1, 10_000_000).toArray();
ForkJoinSumCalculator calculator = new ForkJoinSumCalculator();
long result = calculator.calculateSum(numbers);
System.out.println("Fork/Join sum: " + result);

Program output:

Fork/Join sum: 50000005000000
Expected sum:  50000005000000
Correct: true
Time taken: 45 ms
Available processors: 8

When to Use the Fork/Join Pattern in Java

  • When you have a large, CPU-intensive task that can be divided into independent subtasks.
  • When the subtasks are roughly the same size and don't depend on each other.
  • When you want to utilize multiple CPU cores without manually managing threads.
  • When the problem naturally fits a divide-and-conquer strategy (e.g., sorting, searching, numerical computation).

When NOT to Use Fork/Join

  • For I/O-bound tasks (network calls, file reads) — use virtual threads or async I/O instead.
  • When subtasks are too small — the overhead of forking exceeds the benefit.
  • When tasks have dependencies on each other and cannot run independently.

Benefits and Trade-offs of Fork/Join Pattern

Benefits:

  • Maximizes CPU utilization through work-stealing algorithm.
  • Scales automatically with the number of available processors.
  • Built into Java's standard library (java.util.concurrent) — no external dependencies.
  • Clean recursive decomposition makes the code readable and maintainable.

Trade-offs:

  • Overhead from task creation and thread management for very small problems.
  • Requires tasks to be independent — shared mutable state introduces bugs.
  • Choosing an appropriate threshold requires tuning for optimal performance.
  • Debugging parallel code is inherently harder than sequential code.
  • Divide and Conquer: Fork/Join is the parallel execution variant of the classic divide-and-conquer strategy.
  • Thread Pool: Fork/Join uses a specialized pool with work-stealing semantics.

References