Mastering Java ThreadPoolExecutor Rejection Policies: When Tasks Get Dropped

This guide explains the five ThreadPoolExecutor rejection policies in Java—AbortPolicy, CallerRunsPolicy, DiscardPolicy, DiscardOldestPolicy, and custom handlers—detailing their behavior, use‑cases, and providing concrete code examples for each.

Architect Chen
Architect Chen
Architect Chen
Mastering Java ThreadPoolExecutor Rejection Policies: When Tasks Get Dropped

Java's ThreadPoolExecutor uses rejection policies to decide what happens when the pool and its queue are full. Understanding these policies helps you choose the right strategy for your application's reliability and performance.

1. AbortPolicy (default)

When the pool cannot accept new tasks, it throws a RejectedExecutionException, immediately rejecting the task.

ThreadPoolExecutor executor = new ThreadPoolExecutor(
    1, 1, 0, TimeUnit.SECONDS,
    new ArrayBlockingQueue<>(1),
    new ThreadPoolExecutor.AbortPolicy());

executor.submit(() -> System.out.println("Task 1"));
executor.submit(() -> System.out.println("Task 2")); // Rejected

This policy notifies the caller that the executor is saturated.

2. CallerRunsPolicy

If the pool is full, the task is executed in the thread that submitted it, avoiding immediate rejection but potentially slowing the caller.

ThreadPoolExecutor executor = new ThreadPoolExecutor(
    1, 1, 0, TimeUnit.SECONDS,
    new ArrayBlockingQueue<>(1),
    new ThreadPoolExecutor.CallerRunsPolicy());

executor.submit(() -> System.out.println("Task 1"));
executor.submit(() -> {
    System.out.println("Task 2 (CallerRunsPolicy)");
    System.out.println("Executed by: " + Thread.currentThread().getName());
});

Use this policy when you can tolerate the caller thread doing extra work.

3. DiscardPolicy

New tasks are silently dropped without any exception or execution.

ThreadPoolExecutor executor = new ThreadPoolExecutor(
    1, 1, 0, TimeUnit.SECONDS,
    new ArrayBlockingQueue<>(1),
    new ThreadPoolExecutor.DiscardPolicy());

executor.submit(() -> System.out.println("Task 1"));
executor.submit(() -> System.out.println("Task 2")); // Discarded

Suitable for low‑priority work where occasional loss is acceptable, such as logging or metrics collection.

4. DiscardOldestPolicy

The oldest task in the queue is removed to make room for the new task.

ThreadPoolExecutor executor = new ThreadPoolExecutor(
    1, 1, 0, TimeUnit.SECONDS,
    new ArrayBlockingQueue<>(1),
    new ThreadPoolExecutor.DiscardOldestPolicy());

executor.submit(() -> System.out.println("Task 1"));
executor.submit(() -> System.out.println("Task 2")); // Task 1 discarded, Task 2 queued

Useful when newer tasks have higher priority than older ones.

5. Custom Rejection Policy

You can implement RejectedExecutionHandler to define any behavior you need.

class CustomRejectedExecutionHandler implements RejectedExecutionHandler {
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        System.out.println("Custom Rejected Execution: " + r.toString());
    }
}

ThreadPoolExecutor executor = new ThreadPoolExecutor(
    1, 1, 0, TimeUnit.SECONDS,
    new ArrayBlockingQueue<>(1),
    new CustomRejectedExecutionHandler());

executor.submit(() -> System.out.println("Task 1"));
executor.submit(() -> System.out.println("Task 2")); // Triggers custom handler

Choose the policy that best matches your application's tolerance for task loss, latency, and caller impact.

JavaThreadPoolmultithreadingExecutorRejectionPolicy
Architect Chen
Written by

Architect Chen

Sharing over a decade of architecture experience from Baidu, Alibaba, and Tencent.

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.