Backend Development 13 min read

Understanding Java ThreadPoolExecutor Rejection Policies and Their Implementations

This article explains the design of thread pools, when Java's ThreadPoolExecutor triggers rejection policies, details the four built‑in JDK policies and compares several third‑party implementations such as Dubbo, Netty, ActiveMQ and Pinpoint, helping developers choose the appropriate strategy for different scenarios.

Top Architect
Top Architect
Top Architect
Understanding Java ThreadPoolExecutor Rejection Policies and Their Implementations

Java's ExecutorService and the ThreadPoolExecutor simplify multithreaded programming, but when the pool cannot accept more tasks it applies a rejection policy. The article first introduces the general concept of pool design, illustrating how resources like threads, JDBC connections, or Redis connections are pre‑allocated to avoid the cost of creating them on each request.

Thread pools differ from connection pools because they also contain a blocking queue. A rejection occurs only after the core pool size is exceeded, tasks fill the queue, and finally the maximum pool size is reached; at that point the configured RejectedExecutionHandler is invoked.

JDK Built‑in Rejection Policies

The JDK defines four standard policies:

CallerRunsPolicy

public interface RejectedExecutionHandler {
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

public static class CallerRunsPolicy implements RejectedExecutionHandler {
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {
            r.run();
        }
    }
}

When triggered, the submitting thread runs the task directly, suitable for low‑concurrency scenarios where task loss is unacceptable.

AbortPolicy

public static class AbortPolicy implements RejectedExecutionHandler {
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        throw new RejectedExecutionException("Task " + r.toString() + " rejected from " + e.toString());
    }
}

This is the default policy; it throws a RejectedExecutionException , immediately aborting the task.

DiscardPolicy

public static class DiscardPolicy implements RejectedExecutionHandler {
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        // silently discard
    }
}

The task is dropped without any notification, useful only when the work is truly optional.

DiscardOldestPolicy

public static class DiscardOldestPolicy implements RejectedExecutionHandler {
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {
            e.getQueue().poll();
            e.execute(r);
        }
    }
}

The oldest waiting task is removed from the queue and the new task is submitted, fitting scenarios where newer work supersedes older work.

Third‑Party Implementations

Dubbo – AbortPolicyWithReport

public class AbortPolicyWithReport extends ThreadPoolExecutor.AbortPolicy {
    private final String threadName;
    private final URL url;
    private static volatile long lastPrintTime = 0;
    private static Semaphore guard = new Semaphore(1);
    public AbortPolicyWithReport(String threadName, URL url) { ... }
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        String msg = String.format("Thread pool is EXHAUSTED! Thread Name: %s, Pool Size: %d ...", ...);
        logger.warn(msg);
        dumpJStack();
        throw new RejectedExecutionException(msg);
    }
    private void dumpJStack() { /* omitted */ }
}

Dubbo logs detailed pool state, dumps the stack trace, and then re‑throws the exception, providing rich diagnostics for production operators.

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 fresh thread, allowing higher throughput at the cost of extra thread creation.

ActiveMQ – Timed Queue Offer

new RejectedExecutionHandler() {
    @Override
    public void rejectedExecution(Runnable r, 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.");
    }
};

ActiveMQ attempts to enqueue the task for up to one minute before giving up, favoring eventual execution when possible.

Pinpoint – RejectedExecutionHandlerChain

public class RejectedExecutionHandlerChain implements RejectedExecutionHandler {
    private final RejectedExecutionHandler[] handlerChain;
    public static RejectedExecutionHandler build(List
chain) {
        Objects.requireNonNull(chain, "handlerChain must not be null");
        return new RejectedExecutionHandlerChain(chain.toArray(new RejectedExecutionHandler[0]));
    }
    @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 a rejection occurs.

In conclusion, the article walks through the pool design philosophy, explains when rejection happens, enumerates JDK's four built‑in policies, and surveys four open‑source implementations, giving readers a comprehensive view to select and possibly extend rejection strategies for their own Java applications.

BackendJavaConcurrencyThreadPoolExecutorRejectionPolicy
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.