๐ด The Error You're Seeing
Confirm this matches your console output. If it does, you're in the right place.
2026-02-20 12:15:30.812 ERROR 8842 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask rejected from java.util.concurrent.ThreadPoolExecutor] with root cause
java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask rejected from java.util.concurrent.ThreadPoolExecutor[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0]โก Quick Fix Works 80% of the time
Increase the queueCapacity of your ThreadPoolTaskExecutor.
@Bean
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setQueueCapacity(500); // Explicit bounded queue (default is unbounded Integer.MAX_VALUE)
return executor;
}๐ง Why this Happens
Tap to expand the deep technical explanation
You submitted a background task (e.g., via `@Async`), but the thread pool's core threads are all busy, and the internal queue holding pending tasks is completely full. The thread pool's RejectedExecutionHandler stepped in and rejected the task to prevent the system from running out of memory.
The HITEC City Parking Spot Analogy:
It's like a restaurant kitchen. All the chefs (core threads) are busy cooking, and the counter holding order tickets (queue) is completely full. The waiter trying to put down a new ticket is told 'We cannot accept any more orders right now.'
๐ How to Reproduce Confirm this is your error
Create a `ThreadPoolTaskExecutor` with `setCorePoolSize(1)` and `setQueueCapacity(1)`. Trigger two `@Async` methods simultaneously. The second method will be rejected because the 1 thread is busy and the 1-slot queue is full.
๐ ๏ธ Solutions (5 Ways to Fix)
Increase queue capacity
๐ Use this if you experience sudden bursts of traffic but can process them eventually.
A larger queue allows more tasks to wait patiently for a thread to become available.
@Bean
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100); // Hold up to 100 pending tasks
executor.initialize();
return executor;
}Increase max pool size
๐ Use this if your tasks are CPU intensive and you have server capacity.
Allow the pool to spawn new threads when the queue is full, up to the max pool size.
executor.setMaxPoolSize(50); // Spawn up to 50 threads before rejectingChange the Rejection Policy
๐ Use this if you want the calling thread to run the task instead of rejecting it.
Set the RejectedExecutionHandler to `CallerRunsPolicy`. The Tomcat HTTP thread will execute the async task itself, slowing down the client but preventing data loss.
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());Fix thread leaks / infinite loops
๐ Use this if threads are never returning to the pool.
If your @Async method has an infinite loop or a very long blocking call, threads get stuck. Ensure tasks eventually complete.
@Async
public void process() {
// Ensure there is no while(true) blocking forever
// and that external API calls have timeouts.
}Do not shut down the executor prematurely
๐ Use this if the error shows 'Terminated, pool size = 0'.
If you call `executor.shutdown()` somewhere in your code, the pool stops accepting tasks. Let Spring manage the lifecycle.
// Remove any manual calls to executor.shutdown();๐ Version Notes
Default async executor uses SimpleAsyncTaskExecutor (no pooling).
Encourages explicit ThreadPoolTaskExecutor configuration.
๐ก๏ธ How to Prevent This Next Time
Monitor your thread pools in production using Spring Boot Actuator metrics. Right-size your pools based on your task duration and traffic.