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.
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
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:
- '
AbortPolicythrowsRejectedExecutionException' - '
CallerRunsPolicyruns the task in the caller thread' - '
DiscardPolicydrops the task silently' - '
DiscardOldestPolicydrops 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
- '
RejectedExecutionExceptionmeans 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.

