There actually is a trick how to execute a parallel operation in a specific fork-join pool. If you execute it as a task in a fork-join pool, it stays there and does not use the common one.

final int parallelism = 4;
ForkJoinPool forkJoinPool = null;
try {
    forkJoinPool = new ForkJoinPool(parallelism);
    final List<Integer> primes = forkJoinPool.submit(() ->
        // Parallel task here, for example
        IntStream.range(1, 1_000_000).parallel()
                .filter(PrimesPrint::isPrime)
                .boxed().collect(Collectors.toList())
    ).get();
    System.out.println(primes);
} catch (InterruptedException | ExecutionException e) {
    throw new RuntimeException(e);
} finally {
    if (forkJoinPool != null) {
        forkJoinPool.shutdown();
    }
}

The trick is based on ForkJoinTask.fork which specifies: "Arranges to asynchronously execute this task in the pool the current task is running in, if applicable, or using the ForkJoinPool.commonPool() if not inForkJoinPool()"

Answer from Lukáš Křečan on Stack Overflow
Top answer
1 of 16
520

There actually is a trick how to execute a parallel operation in a specific fork-join pool. If you execute it as a task in a fork-join pool, it stays there and does not use the common one.

final int parallelism = 4;
ForkJoinPool forkJoinPool = null;
try {
    forkJoinPool = new ForkJoinPool(parallelism);
    final List<Integer> primes = forkJoinPool.submit(() ->
        // Parallel task here, for example
        IntStream.range(1, 1_000_000).parallel()
                .filter(PrimesPrint::isPrime)
                .boxed().collect(Collectors.toList())
    ).get();
    System.out.println(primes);
} catch (InterruptedException | ExecutionException e) {
    throw new RuntimeException(e);
} finally {
    if (forkJoinPool != null) {
        forkJoinPool.shutdown();
    }
}

The trick is based on ForkJoinTask.fork which specifies: "Arranges to asynchronously execute this task in the pool the current task is running in, if applicable, or using the ForkJoinPool.commonPool() if not inForkJoinPool()"

2 of 16
251

The parallel streams use the default ForkJoinPool.commonPool which by default has one less threads as you have processors, as returned by Runtime.getRuntime().availableProcessors() (This means that parallel streams leave one processor for the calling thread).

For applications that require separate or custom pools, a ForkJoinPool may be constructed with a given target parallelism level; by default, equal to the number of available processors.

This also means if you have nested parallel streams or multiple parallel streams started concurrently, they will all share the same pool. Advantage: you will never use more than the default (number of available processors). Disadvantage: you may not get "all the processors" assigned to each parallel stream you initiate (if you happen to have more than one). (Apparently you can use a ManagedBlocker to circumvent that.)

To change the way parallel streams are executed, you can either

  • submit the parallel stream execution to your own ForkJoinPool: yourFJP.submit(() -> stream.parallel().forEach(soSomething)).get(); or
  • you can change the size of the common pool using system properties: System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "20") for a target parallelism of 20 threads.

Example of the latter on my machine which has 8 processors. If I run the following program:

long start = System.currentTimeMillis();
IntStream s = IntStream.range(0, 20);
//System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "20");
s.parallel().forEach(i -> {
    try { Thread.sleep(100); } catch (Exception ignore) {}
    System.out.print((System.currentTimeMillis() - start) + " ");
});

The output is:

215 216 216 216 216 216 216 216 315 316 316 316 316 316 316 316 415 416 416 416

So you can see that the parallel stream processes 8 items at a time, i.e. it uses 8 threads. However, if I uncomment the commented line, the output is:

215 215 215 215 215 216 216 216 216 216 216 216 216 216 216 216 216 216 216 216

This time, the parallel stream has used 20 threads and all 20 elements in the stream have been processed concurrently.

Discussions

Is it possible to use virtual threads with parallel streams?
Parallel streams don't have to use the global fork join pool. If you use them in the context of your own fork join pool, that's the one they will use More on reddit.com
🌐 r/java
20
37
March 22, 2023
Custom Thread Pools In Java Parallel Streams
Wish I read this before starting my current project. I was getting crazy memory leaks before I found out you had to shutdown the thread pools. Good article for beginners in parallel processing More on reddit.com
🌐 r/java
6
36
February 14, 2023
Alternating between Java streams and parallel streams at runtime - Software Engineering Stack Exchange
I don't understand why this approach for the parallel stream should run in parallel inside the pool instead of inside the global pool? Thorbjørn Ravn Andersen – Thorbjørn Ravn Andersen · 2021-02-12 18:58:00 +00:00 Commented Feb 12, 2021 at 18:58 · @ThorbjørnRavnAndersen Can you clarify what you mean by 'global thread pool'? This doesn't seem to be a standard term with regard to Java... More on softwareengineering.stackexchange.com
🌐 softwareengineering.stackexchange.com
Pitfalls Of Java Parallel Streams
This is one of my main frustrations with Java streams. The API is harder to use than it could be to ensure that operations can be parallelised, but I almost never want parallel execution. More on reddit.com
🌐 r/programming
12
10
June 4, 2021
🌐
Medium
medium.com › geekculture › pitfalls-of-java-parallel-streams-731fe0c1eb5f
Internals Of Java Parallel Streams | by Thameena S | Geek Culture | Medium
July 1, 2021 - When tasks are run in parallel using Java parallel streams, it internally uses threads from the default thread pool of ForkJoinPool called the commonPool(), which is a static thread pool.
🌐
Java Code Geeks
javacodegeeks.com › home › core java
Java 8 Parallel Streams - Custom Thread Pools Examples - Java Code Geeks
April 28, 2021 - If you have only one parallel stream then you can use it with a limited pool count. But, Wait for a java update that parallel stream can take ForkJoinPool as input to limit the number of parallel processes. In this article, You’ve seen how to create parallel streams in java stream api and parallel stream api uses a common share thread pool from ForkJoinPool.
🌐
DEV Community
dev.to › igalhaddad › java-8-parallel-stream-with-threadpool-32kd
Java 8 Parallel Stream with ThreadPool - DEV Community
February 10, 2020 - When executing a parallel stream, ... all other parallel streams. Sometimes we want to execute code in parallel on a separate dedicated thread pool, constructed with a specific number of threads....
🌐
Baeldung
baeldung.com › home › java › java streams › when to use a parallel stream in java
When to Use a Parallel Stream in Java | Baeldung
November 10, 2025 - In this article, we explored the difference between sequential and parallel streams in Java. We learned that parallel streams make use of the default fork-join pool and its worker threads.
Find elsewhere
🌐
Javaprogramto
javaprogramto.com › 2020 › 05 › java-8-parallel-streams-custom-threadpool.html
Java 8 Parallel Streams - Custom Thread Pools Examples JavaProgramTo.com
May 2, 2021 - In these cases, It is good to go with the custom thread pools with the parallel streams combination. Look at the below program, that runs with 5 threads using ForkJoinPool and inside creating a new parallel stream to find the sum of all numbers for the given range. package com.javaprogramto.java8.streams.parallel.streams; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.ForkJoinPool; import java.util.stream.Collectors; import java.util.stream.IntStream; public class CustomPoolParallelStreams { public static void main(String[] args) throws Execu
🌐
Reddit
reddit.com › r/java › custom thread pools in java parallel streams
r/java on Reddit: Custom Thread Pools In Java Parallel Streams
February 14, 2023 - News, Technical discussions, research papers and assorted things of interest related to the Java programming language NO programming help, NO learning Java related questions, NO installing or downloading Java questions, NO JVM languages - Exclusively Java ... Archived post. New comments cannot be posted and votes cannot be cast. Share ... Wish I read this before starting my current project. I was getting crazy memory leaks before I found out you had to shutdown the thread pools. Good article for beginners in parallel processing
🌐
Glassthought
glassthought.com › notes › lc2wo9z3twpqypqncncbgzd
Parallel Streams with Custom Thread Pool - GlassThought.com
December 21, 2023 - import java.util.List; import java.util.concurrent.ForkJoinPool; import java.util.stream.Collectors; public class ParallelStreamExample { public static void main(String[] args) { List<Integer> data = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); int numberOfThreads = 4; // Specify the number of threads ForkJoinPool customThreadPool = new ForkJoinPool(numberOfThreads); try { // Submit the parallel stream execution to the custom thread pool List<Integer> result = customThreadPool.submit( () -> data.parallelStream() .map(ParallelStreamExample::process) .collect(Collectors.toList()) ).join(); // This will wait for all tasks to complete System.out.println("Processed data: " + result); } finally { customThreadPool.shutdown(); // Always shutdown the thread pool } } private static int process(int input) { // Example processing (this could be any operation) return input * input; } }
🌐
Javacodemonk
javacodemonk.com › java-8-parallel-stream-custom-threadpool-48643a91
Java 8 Parallel Stream custom ThreadPool
August 23, 2020 - public class CustomCommonPoolSize { public void testParallelOperation() { long start = System.currentTimeMillis(); IntStream s = IntStream.range(0, 20); System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "20"); s.parallel().forEach(i -> { try { Thread.sleep(100); } catch (Exception ignore) {} System.out.print((System.currentTimeMillis() - start) + " ms"); }); System.out.println("\nOverall time consumed: "+ (System.currentTimeMillis() - start)+" ms"); } } ... 192 192 192 192 192 192 192 192 192 192 192 192 192 192 192 192 192 192 192 192 Overall time consumed: 193 ms · Se see that all 20 tasks run in parallel and this the overall time is just 193 ms, even if individual task was taking 192ms each.
🌐
Geeky Hacker
geekyhacker.com › home › java › control threads number in java parallel stream
Control threads number in Java parallel stream - Geeky Hacker
March 30, 2024 - There are two ways to control threads number in Java parallel stream. One using configuration and another by using a custom fork-join pool.
🌐
GitHub
github.com › ferstl › parallel-stream-support
GitHub - ferstl/parallel-stream-support: Parallel streams in Java with a custom ForkJoinPool · GitHub
If the operation was already started in a ForkJoinPool it will use that pool to complete the operation. This library utilizes this behavior and makes sure that terminal operations are started as task in the user-defined ForkJoinPool. Q: Does this library also support sequential streams? A: Yes. Just call sequential() on the stream and it will be processed within the calling thread. When created, all streams of this library are configured to be parallel. Q: Why is there no public constructor which takes a regular Java stream and a ForkJoinPool?
Starred by 31 users
Forked by 3 users
Languages   Java
Top answer
1 of 3
5

You can define a custom thread pool by implementing the (Executor) interface that increases or decreases the number of threads in the pool as needed. You can submit your parallelStream chain to it as shown here using a ForkJoinPool:

I've created a working example which prints the threads that are doing the work:

import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.stream.Collectors;
import java.util.stream.LongStream;

public class TestParallel
{
  public static void main(String... args) throws InterruptedException, ExecutionException
  {
    testParallel();
  }
  
  
  static Long sum(long a, long b)
  {
    System.out.println(Thread.currentThread() + " - sum: " + a + " " + b);
    return a + b;
  }
  
  public static void testParallel() 
      throws InterruptedException, ExecutionException {
        
        long firstNum = 1;
        long lastNum = 10;

        List<Long> aList = LongStream.rangeClosed(firstNum, lastNum).boxed()
          .collect(Collectors.toList());

        System.out.println("custom: ");
        System.out.println();
        
        ForkJoinPool customThreadPool = new ForkJoinPool(4);
        long totalCustom = customThreadPool.submit(
          () -> aList.parallelStream().reduce(0L, TestParallel::sum)).get();
        
        System.out.println();
        System.out.println("standard: ");
        System.out.println();
        
        long totalStandard = aList.parallelStream().reduce(0L, TestParallel::sum);
        
        System.out.println();
        System.out.println(totalCustom + " " + totalStandard);
    }
}

Personally, if you want to get to that level of control, I'm not sure the streaming API is worth bothering with. It's not doing anything you can't do with Executors and concurrent libs. It's just a simplified facade to those features with limited capabilities.

Streams are kind of nice when you need to lay out a simple multi-step process in a little bit of code. But if all you are doing is using them to manage parallelism of tasks, the Executors and ExecutorService are more straightforward IMO. One thing I would avoid is pushing the number of threads above your machine's native thread count unless you have IO-bound processing. And if that's the case NIO is the more efficient solution.

What I'm not sure about is what the logic is that decides when to use multiple threads and when to use one. You'd have to better explain what factors come into play.

2 of 3
2

I don't know if this is useful but there is a design pattern called Bridge that decouples the abstraction from its implementation so you can, at runtime change between implementations.

A simple example would be a stack. For stacks where the total amount of data stored at one time is relatively small, it is more efficient to use an array. When the amount of data hits a certain point, it becomes better to use a linked-list. The stack implementation determines when it switches from one to the other.

For your case, it sounds like the processing would be behind some interface and based on the volume (do you know it before you start the processing?) your Processor class could use streams or parallel streams as appropriate.

🌐
Java2Blog
java2blog.com › home › core java › java 8 › java parallel stream
Java Parallel Stream - Java2Blog
January 26, 2021 - The parallel stream by default uses ForkJoinPool.commonPool which has one less thread than number of processor.
🌐
TheServerSide
theserverside.com › tip › How-to-use-parallel-streams-in-Java-with-virtual-threads
How to use parallel streams in Java with virtual threads | TheServerSide
July 3, 2024 - Parallel streams utilize the fork/join pool which defaults to the number of available cores. This can help improve performance, but it's limited by the pool size and the fact that this is I/O-bound work.
🌐
Medium
medium.com › @thecodealchemistX › java-parallel-streams-and-the-fork-join-pool-what-every-developer-needs-to-understand-ec549803e414
Java Parallel Streams and the Fork/Join Pool: What Every Developer Needs to Understand | by The Code Alchemist | Medium
May 18, 2025 - The Fork/Join Pool uses a work-stealing algorithm: each thread has its own queue of tasks, and idle threads “steal” tasks from busy threads’ queues. This minimizes contention and maximizes CPU utilization. Implication: This makes Parallel Streams efficient for divide-and-conquer tasks (e.g., recursive computations), but less so for I/O-bound tasks.
🌐
Xperti
xperti.io › home › when to use the parallel stream in java
When To Use The Parallel Stream In Java
May 2, 2022 - A Java Parallel stream uses the fork-join framework and its common pool of worker threads to perform parallel executions. This framework was introduced in java.util.concurrent in Java 7 for task management between multiple threads.
🌐
Medium
medium.com › @daniel.las › performance-of-parallel-java-streams-68988191d9f8
Performance of parallel Java Streams | by Daniel Las | Medium
April 21, 2024 - Benchmark results are presented as charts showing number of operations per second of sequential and parallel streams running in 2, 4, 6, 8, 10,12, 14 and 16 threads. The number of operations is the sum of operations executed in every running thread within one second. If you wonder if this setup reflects the reality of web applications … this is how modern Non-Blocking I/O based web frameworks work: there is some thread responsible for I/O and passing tasks to multiple worker threads waiting in the pool.
🌐
Harness
harness.io › blog › service reliability management › fork/join framework vs. parallel streams vs. executorservice: the ultimate fork/join benchmark
Fork/Join Framework vs. Parallel Streams vs. ExecutorService
3 weeks ago - Parallel streams in Java 8 offer ... compared to single-threaded solutions, but require careful configuration of thread pool sizes to avoid overhead and performance degradation....