Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Java bounded executors: test saturation and rejected work

Last updated: 30 Sept 20264 min read
tutorial
IntermediateBy AITrove Editorial

A ThreadPoolExecutor with a bounded queue can reject a submitted task when its workers and queue cannot accept more work.

Download Java source kit

The complete program targets Java 8. Compile it as one source file; its output is checked against the lesson.

Backlog needs a limit

An importer with one worker and one queue slot can hold one running task and one waiting task. A third submission must meet an explicit rejection rule. An unbounded queue changes the failure mode to growing memory and delay; it does not create extra processing capacity.

The fixture blocks the worker on a latch, fills the queue, and checks AbortPolicy rejection. It then releases the worker in finally. That arrangement controls saturation without relying on sleeps or guessing how quickly a worker starts.

Choose what the caller observes

CallerRunsPolicy moves execution onto the submitting thread, which can slow admission but may also block an HTTP request or event-loop thread. Silently discarding a task can lose work while the caller believes it was accepted. Neither policy is universally correct.

A queue limit does not supply durable storage. If the JVM exits, queued work disappears. A service that promises eventual processing needs persistence and replay semantics outside this local executor demonstration.

Working program

Java
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ImportAdmission {
    public static void main(String[] args) throws Exception {
        CountDownLatch release = new CountDownLatch(1);
        ThreadPoolExecutor worker = new ThreadPoolExecutor(1, 1, 0, TimeUnit.SECONDS,
            new ArrayBlockingQueue<Runnable>(1), new ThreadPoolExecutor.AbortPolicy());
        try {
            worker.execute(() -> { try { release.await(); } catch (InterruptedException stop) { Thread.currentThread().interrupt(); } });
            worker.execute(() -> {});
            try { worker.execute(() -> {}); }
            catch (RejectedExecutionException rejected) { System.out.println("capacity rejected"); }
            System.out.println("queued=" + worker.getQueue().size());
        } finally { release.countDown(); worker.shutdown(); }
        if (!worker.awaitTermination(5, TimeUnit.SECONDS)) throw new AssertionError("worker did not stop");
    }
}

Output

Output
capacity rejected
queued=1

Costs and boundaries

This queue holds at most one task and the pool has one worker. Retained task closures can still reference large payloads, so a bounded task count is not an exact byte budget. This test checks admission and termination, not production throughput.

Common Mistakes

  • Do not hide rejected work behind a success response.
  • Bound payload size as well as queue length.
  • Release test latches even when an assertion fails.

Read next

Java ExecutorService: bounded admission and shutdown, Java blocking queues: bounded capacity and backpressure, Async boundaries.

Compare the Python boundary

Python thread pools: collect results and observe worker failures.

java
executor-rejection
Storage details