A bounded ThreadPoolTaskExecutor can reject a new task when its worker and queue are both occupied.
Spring task executors: reject work when every slot is occupied
Make saturation observable
The source-kit fixture configures one worker, one waiting slot and the default rejection policy. The first receipt dispatch blocks behind a test latch; the second waits in the queue. A third submission throws TaskRejectedException. After release, both accepted tasks finish. That is a check of admission, not delivery.
Spring queues work after core threads are occupied and grows beyond the core size only after a finite queue fills. With a fixed one-thread pool there is no extra worker to create. An unbounded queue can make a configured maximum pool size irrelevant and move overload into memory. The related future lesson covers errors after admission; this lesson covers a failure at submission.
Map rejection to a real contract
A request handler that catches rejection must say whether it persisted a command before returning. If the command exists in an outbox, the worker can retry later. If it only existed as an in-memory Runnable, a successful HTTP acceptance claim would be false. Do not silently switch to CallerRunsPolicy unless the request thread is allowed to execute potentially slow work and its deadline accounts for that cost.
Checked source
executor.setCorePoolSize(1);
executor.setMaxPoolSize(1);
executor.setQueueCapacity(1);
executor.initialize();
Future<?> first = executor.submit(blockedReceiptDispatch);
Future<?> second = executor.submit(queuedReceiptDispatch);
assertThrows(TaskRejectedException.class,
() -> executor.submit(overflowReceiptDispatch));Verification boundary
BoundedExecutorContractTest.thirdTaskIsRejectedWhileOneRunsAndOneWaits runs in the downloadable Spring source kit. The excerpt is shortened; the kit contains the complete tests.
Costs and limits
This local test checks one configuration and one saturated instant. Queue memory is O(capacity), plus task-held objects. It does not measure throughput, fairness, shutdown drain, HTTP status mapping or behavior under several application replicas.
Common Mistakes
- Do not use an unbounded queue when admission must have a limit.
- Do not report accepted work after an executor rejected it.
- Do not treat an executor queue as restart-safe storage.
Read next
Spring @Async futures: make worker failure observable to the caller, Spring async work versus durable delivery: separate latency from recovery, Spring transactional outbox: commit a receipt and event row together.
