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<RejectedExecutionHandler> 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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

javaconcurrencyThreadPoolExecutorRejectionPolicy
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

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.