Mastering Java ThreadPoolExecutor Rejection Policies: When and How to Use Them
This article explains the design principles of thread pools, when Java's ThreadPoolExecutor triggers rejection policies, details the four built‑in JDK policies with code examples, and explores additional strategies from Dubbo, Netty, ActiveMQ and Pinpoint, helping developers choose the right approach for their workloads.
Preface
When discussing Java thread pools, the most familiar API is ExecutorService, introduced in JDK 1.5 under java.util.concurrent. Whether you use a fixed or cached thread pool, the underlying implementation is ThreadPoolExecutor. ThreadPoolExecutor follows a typical pooled design: a fixed size pool, a queue for pending tasks, and a rejection policy that activates when the pool cannot accept more work.
Pool Design Philosophy
Pooling is common in Java thread pools, JDBC connection pools, Redis connection pools, etc. It pre‑allocates resources to avoid the overhead of creating them on each request, similar to a cafeteria where meals are prepared in advance.
Key pool attributes include core size, maximum size, and queue capacity, which map directly to ThreadPoolExecutor fields.
When Does a Thread Pool Trigger a Rejection Policy?
Unlike data source pools, a thread pool also has a blocking queue. Rejection occurs only after the core pool is full, the queue is saturated, and the maximum pool size is reached. In other words, rejection is triggered when submittedTasks > (maxPoolSize + queueCapacity).
JDK Built‑in Rejection Policies
RejectedExecutionHandler Interface
public interface RejectedExecutionHandler {
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}CallerRunsPolicy (Caller‑Runs Policy)
public static class CallerRunsPolicy implements RejectedExecutionHandler {
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}Function: The submitting thread runs the task if the pool is not shut down.
Use case: Suitable for low‑concurrency scenarios where task failure is unacceptable and performance requirements are modest.
AbortPolicy (Abort Policy)
public static class AbortPolicy implements RejectedExecutionHandler {
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() + " rejected from " + e.toString());
}
}Function: Throws a RejectedExecutionException, aborting the current execution flow.
Use case: Default JDK policy; callers must handle the exception.
Note: The default queues in ExecutorService are unbounded, so they rarely trigger rejection unless a custom bounded queue is used.
DiscardPolicy (Discard Policy)
public static class DiscardPolicy implements RejectedExecutionHandler {
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
// silently discard
}
}Function: Silently discards the task.
Use case: When the task is non‑essential and can be dropped without impact.
DiscardOldestPolicy (Discard‑Oldest Policy)
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
}Function: Removes the oldest queued task and attempts to execute the new one.
Use case: Useful when newer tasks have higher priority than older pending ones.
Third‑Party Rejection Policy Implementations
Dubbo – AbortPolicyWithReport
public class AbortPolicyWithReport extends ThreadPoolExecutor.AbortPolicy {
private final String threadName;
private final URL url;
// ... other fields omitted
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
String msg = String.format(
"Thread pool is EXHAUSTED! Thread Name: %s, Pool Size: %d (active: %d, core: %d, max: %d, largest: %d), " +
"Task: %d (completed: %d), Executor status:(isShutdown:%s, isTerminated:%s, isTerminating:%s), in %s://%s:%d!",
threadName, e.getPoolSize(), e.getActiveCount(), e.getCorePoolSize(), e.getMaximumPoolSize(),
e.getLargestPoolSize(), e.getTaskCount(), e.getCompletedTaskCount(), e.isShutdown(),
e.isTerminated(), e.isTerminating(), url.getProtocol(), url.getIp(), url.getPort());
logger.warn(msg);
dumpJStack();
throw new RejectedExecutionException(msg);
}
private void dumpJStack() { /* implementation omitted */ }
}Dubbo logs detailed pool parameters, dumps the thread stack, and then throws the exception.
Netty – NewThreadRunsPolicy
private static final class NewThreadRunsPolicy implements RejectedExecutionHandler {
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
try {
Thread t = new Thread(r, "Temporary task executor");
t.start();
} catch (Throwable e) {
throw new RejectedExecutionException("Failed to start a new thread", e);
}
}
}Similar to CallerRunsPolicy but creates a new thread for the rejected task, suitable for high‑performance scenarios.
ActiveMQ – Custom Handler
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(final Runnable r, final ThreadPoolExecutor executor) {
try {
executor.getQueue().offer(r, 60, TimeUnit.SECONDS);
} catch (InterruptedException e) {
throw new RejectedExecutionException("Interrupted waiting for BrokerService.worker");
}
throw new RejectedExecutionException("Timed Out while attempting to enqueue Task.");
}
}This policy tries to re‑queue the task for up to one minute before giving up.
Pinpoint – RejectedExecutionHandlerChain
public class RejectedExecutionHandlerChain implements RejectedExecutionHandler {
private final RejectedExecutionHandler[] handlerChain;
public static RejectedExecutionHandler build(List<RejectedExecutionHandler> chain) {
Objects.requireNonNull(chain, "handlerChain must not be null");
return new RejectedExecutionHandlerChain(chain.toArray(new RejectedExecutionHandler[0]));
}
private RejectedExecutionHandlerChain(RejectedExecutionHandler[] handlerChain) {
this.handlerChain = Objects.requireNonNull(handlerChain, "handlerChain must not be null");
}
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
for (RejectedExecutionHandler h : handlerChain) {
h.rejectedExecution(r, executor);
}
}
}Pinpoint composes multiple handlers into a chain, executing each in order when rejection occurs.
Conclusion
The article introduced thread‑pool design concepts, explained when ThreadPoolExecutor triggers rejection, presented the four JDK built‑in policies, and described four third‑party implementations, providing a comprehensive view of possible strategies and their appropriate use cases.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
