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

Spring task executors: capacity, rejection and lost context

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

A task executor moves work to worker threads, which changes admission, context ownership and failure reporting compared with a direct method call.

Download Spring source kit

This lesson uses the downloadable source kit: Java 21, Spring Boot 4.0.8 and its managed Spring Framework 7 dependencies. The version is pinned for repeatable builds.

Bound pending work

AsyncReceiptWork configures one worker and one queue slot. It blocks the first task with a latch, queues the second and expects rejection of the third. The complete program proves that saturation is visible instead of claiming that asynchronous work is unlimited.

Queue capacity counts tasks, not bytes. A closure can retain a large request payload even if only one task waits. Bound input size and decide whether rejection becomes a retriable response, a durable enqueue failure or another documented outcome.

A new thread changes the boundary

The caller’s transaction and thread-local state do not automatically become the worker’s context. Pass immutable input and explicit identifiers when that makes ownership easier to inspect. Thread-local cleanup matters when workers are reused.

Async annotation interception has the same proxy-entry issue as other advice. Calling an annotated method from the same target is not a reliable way to move work to another thread. The kit uses an executor directly so the tested scheduling boundary is visible.

Checked source

Java
package in.aitrove.learning;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.core.task.TaskRejectedException;
public class AsyncReceiptWork {
    public static void main(String[] args) throws Exception {
        ThreadPoolTaskExecutor workers = new ThreadPoolTaskExecutor();
        workers.setCorePoolSize(1); workers.setMaxPoolSize(1); workers.setQueueCapacity(1); workers.initialize();
        CountDownLatch release = new CountDownLatch(1);
        try {
            workers.execute(() -> { try { release.await(); } catch (InterruptedException stop) { Thread.currentThread().interrupt(); } });
            workers.execute(() -> {});
            try { workers.execute(() -> {}); }
            catch (TaskRejectedException rejected) { System.out.println("capacity rejected"); }
        } finally { release.countDown(); workers.shutdown(); }
        if (!workers.getThreadPoolExecutor().awaitTermination(5, TimeUnit.SECONDS)) throw new AssertionError("termination");
    }
}

Test the boundary

Run mvn test in the source-kit directory. AsyncReceiptWork main program checks the behavior described here. Java excerpts belong to the named source-kit classes; they are not independent source files unless the complete class is shown.

Costs and boundaries

The fixture caps active workers and queued task count. It checks admission and termination, not latency, durability or throughput under a real workload.

Common Mistakes

  • Do not hide rejected work as successful submission.
  • Do not assume a JDBC transaction moves to a worker.
  • Bound retained payload bytes as well as task count.

Read next

Java bounded executors: test saturation and rejected work, Java ThreadLocal: clear request state on reused worker threads, Spring AOP proxies: self-invocation bypasses proxy advice.

Extend this boundary

Continue with Spring reactive foundations: request values and cancel a subscription, Java Phaser: deregister workers so a phase can complete.

Continue with checked Spring boundaries

Continue with Spring @Async futures: make worker failure observable to the caller, Spring async work versus durable delivery: separate latency from recovery.

Continue with checked worker recovery

Continue with Spring task executors: reject work when every slot is occupied, Spring async work has two failure points: submission and completion.

Continue with scheduled relay checks

Continue with Spring TaskScheduler shutdown: stop new polls and account for in-flight work.

spring
spring-boot
async-boundaries
Storage details