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

Spring async work has two failure points: submission and completion

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

An asynchronous command may fail before a worker accepts it or after the worker starts it.

Download Spring source kit

Observe both edges

The bounded executor fixture throws TaskRejectedException on its third submission. A separate managed @Async service returns a failed CompletableFuture whose cause is visible when the caller joins it. These are different failures. A retry on completion is impossible if the caller threw away its future; a retry on submission must not claim that the task was queued.

If HTTP returns before completion, name the response honestly. Durable acceptance requires a committed record such as an outbox row. In-memory acceptance provides only a process-local promise. A receipt ID and a queryable state can bridge that gap, but the state transition and worker must be implemented rather than inferred from @Async. The saturation fixture tests capacity one.

Keep ownership at the boundary

Give the caller or worker a timeout, a cancellation rule and a place to record terminal failure. Cancellation is not proof that a remote side effect did not occur. If the worker sends a message and fails before marking it done, a later replay can send it again. Design the consumer's duplicate rule before calling the delivery path reliable.

Checked source

Java
try {
    Future<?> accepted = dispatchExecutor.submit(receiptDispatch);
    accepted.get(5, TimeUnit.SECONDS);
} catch (TaskRejectedException full) {
    // Nothing entered this executor queue.
} catch (ExecutionException failed) {
    // The accepted worker failed after submission.
}

Verification boundary

BoundedExecutorContractTest.thirdTaskIsRejectedWhileOneRunsAndOneWaits and AsyncFailureContractTest.failureIsObservedThroughTheReturnedFuture runs in the downloadable Spring source kit. The excerpt is shortened; the kit contains the complete tests.

Costs and limits

The excerpts combine two separate local tests. They do not implement one end-to-end dispatcher, a durable queue, a configured production executor or a network timeout. Completion state consumes memory until its owner releases the future.

Common Mistakes

  • Do not merge submission rejection with worker failure in one vague error state.
  • Do not discard an important future.
  • Do not infer durable acceptance from a successful submit call.

Read next

Spring task executors: reject work when every slot is occupied, Spring @Async futures: make worker failure observable to the caller, Spring consumer deduplication: commit the event ID with the mutation.

spring
spring-boot
async-admission-and-completion
Storage details