java concurrency_error ai_generated true

java.util.concurrent.RejectedExecutionException: Task rejected from java.util.concurrent.ThreadPoolExecutor

ID: java/thread-pool-exhausted

Also available as: JSON · Markdown
85%Fix Rate
88%Confidence
60Evidence
2023-01-01First Seen

Version Compatibility

VersionStatusIntroducedDeprecatedNotes
21 active

Root Cause

A ThreadPoolExecutor rejected a submitted task because both the thread pool and its work queue are full. The executor's RejectionPolicy (default AbortPolicy) throws RejectedExecutionException. This indicates the system is receiving tasks faster than it can process them, either due to insufficient pool sizing, slow task execution, or a sudden traffic spike.

generic

Workarounds

  1. 88% success Size the thread pool appropriately and use a bounded queue with CallerRunsPolicy
    For CPU-bound tasks: pool size = number of CPU cores. For I/O-bound tasks: pool size = cores * (1 + wait_time/compute_time), typically 2x-4x cores. Use a bounded ArrayBlockingQueue (e.g., capacity 1000). Set rejection policy to CallerRunsPolicy, which makes the submitting thread execute the task, providing natural backpressure. Example: new ThreadPoolExecutor(cores, cores*2, 60, SECONDS, new ArrayBlockingQueue<>(1000), new CallerRunsPolicy()).
  2. 90% success Use virtual threads (JDK 21+) for I/O-bound workloads to avoid pool exhaustion
    Replace platform thread pools with virtual threads: ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor(). Virtual threads are lightweight (~1KB each) and can scale to millions of concurrent tasks. They are ideal for I/O-bound workloads (HTTP calls, database queries, file I/O). Not suitable for CPU-bound tasks where platform threads with pool sizing are still preferred.

Dead Ends

Common approaches that don't work:

  1. Setting the thread pool size to match the maximum expected concurrent tasks 75% fail

    Creating thousands of platform threads consumes excessive memory (each thread uses ~1MB of stack space). A pool of 10000 threads uses ~10GB of stack memory alone. This does not scale and causes OutOfMemoryError or extreme context switching overhead. Thread pools should be sized based on available CPU cores and task types (CPU-bound vs I/O-bound).

  2. Using an unbounded work queue (LinkedBlockingQueue without capacity) to prevent rejection 70% fail

    An unbounded queue never triggers rejection, so the pool never grows beyond core size. Under sustained high load, the queue grows indefinitely, consuming all heap memory and eventually causing OutOfMemoryError. It also means requests wait in the queue for a very long time, causing client-side timeouts.

Error Chain

Leads to:
Preceded by:
Frequently confused with: