Java
RejectedExecutionException
ThreadPool
Concurrency
Exception Handling

What could be the cause of RejectedExecutionException

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

RejectedExecutionException means a Java executor refused to accept a submitted task. That does not always mean the program is “too busy.” It means the executor’s current state and configuration say “no” to this submission.

The two most common causes are simple: the executor has already been shut down, or the executor has reached its capacity and the configured rejection policy rejects new work. Everything else is usually a variation of one of those two themes.

The Most Common Cause: The Executor Is Shut Down

Once an ExecutorService is shut down, it no longer accepts new tasks.

java
1import java.util.concurrent.ExecutorService;
2import java.util.concurrent.Executors;
3
4public class Demo {
5    public static void main(String[] args) {
6        ExecutorService executor = Executors.newFixedThreadPool(2);
7        executor.shutdown();
8        executor.submit(() -> System.out.println("hello"));
9    }
10}

The submission after shutdown() can trigger RejectedExecutionException.

This often happens in real applications when background components outlive the lifecycle of the executor that was meant to serve them.

Capacity Limits in ThreadPoolExecutor

A ThreadPoolExecutor can also reject tasks when:

  • the pool is at its maximum size
  • the work queue is full
  • the rejection policy is an aborting one
java
1import java.util.concurrent.*;
2
3public class Demo {
4    public static void main(String[] args) {
5        ThreadPoolExecutor executor = new ThreadPoolExecutor(
6            1, 1,
7            0L, TimeUnit.MILLISECONDS,
8            new ArrayBlockingQueue<>(1)
9        );
10
11        executor.submit(() -> sleep());
12        executor.submit(() -> sleep());
13        executor.submit(() -> System.out.println("rejected"));
14    }
15
16    private static void sleep() {
17        try {
18            Thread.sleep(5000);
19        } catch (InterruptedException ignored) {
20        }
21    }
22}

Here the pool has one worker and a queue of size one. The third submission has nowhere to go, so the default AbortPolicy rejects it.

Rejection Policies Matter

ThreadPoolExecutor supports different rejection handlers:

  • 'AbortPolicy throws RejectedExecutionException'
  • 'CallerRunsPolicy runs the task in the caller thread'
  • 'DiscardPolicy drops the task silently'
  • 'DiscardOldestPolicy drops the oldest queued task'

If you see this exception, check whether the rejection handler is the default aborting policy or something custom.

A custom rejection strategy can change the meaning of overload significantly.

Hidden Lifecycle Bugs

In server applications, the exception is often caused by lifecycle mismatch rather than raw traffic volume.

Typical examples:

  • submitting work after Spring or application shutdown started
  • using an executor from a component that has already been closed
  • stale scheduled callbacks firing after the pool was terminated
  • background retries targeting an executor that no longer exists

These are architectural timing bugs, not just sizing problems.

Diagnose Before You Resize

It is tempting to increase thread count or queue size immediately. That may help if the issue is real overload, but it will not fix shutdown misuse.

Check:

  • was the executor already terminated?
  • what is the queue capacity?
  • what is the max pool size?
  • which rejection handler is configured?
  • does the stack trace show lifecycle shutdown timing?

Without those answers, changing pool settings is guesswork.

Common Pitfalls

A common mistake is assuming RejectedExecutionException always means “need more threads.” If the executor is shut down, more threads change nothing.

Another mistake is using an unbounded workload with a tiny bounded queue and then being surprised by rejections under burst traffic.

Developers also forget that shutdown() prevents future submissions but does not kill already accepted tasks immediately.

Finally, silent rejection policies can be worse than the exception because they hide dropped work. The exception is often the useful signal that something is wrong.

Summary

  • 'RejectedExecutionException means the executor refused a task submission.'
  • The usual causes are executor shutdown or capacity limits combined with a rejecting policy.
  • Always inspect executor lifecycle, queue capacity, max threads, and rejection handler.
  • Do not treat every rejection as a pure sizing issue; many are lifecycle bugs.
  • The fix depends on whether the problem is overload, shutdown timing, or misconfigured rejection behavior.

Course illustration
Course illustration

All Rights Reserved.