Asynchronous execution moves work away from the calling thread; durable delivery records work so it can be recovered after a process failure.
Spring async work versus durable delivery: separate latency from recovery
A future is a live-process signal
The source-kit async fixture exposes an IllegalStateException through CompletableFuture.join. That is useful while the process and caller are alive. It does not give a new worker anything to retry after restart. The future lesson explains observation; the outbox lesson commits a pending event with the receipt.
A background worker can use both mechanisms: read a durable event, submit bounded work, inspect its result, then acknowledge under an ownership rule. If the send succeeds but acknowledgement fails, replay may repeat delivery. The consumer therefore needs a stable event ID and duplicate-tolerant behavior. Scheduling or @Async alone cannot close this crash window.
Keep admission visible
A large unbounded executor queue can hide overload until memory pressure appears. Reject or shed work explicitly when capacity is full, and make an HTTP response reflect whether the operation is accepted durably or merely queued in memory. The current source kit demonstrates local executor rejection elsewhere but does not contain a production worker or broker.
Checked source
CompletableFuture<String> result = dispatcher.deliver("R-41");
try {
result.join();
} catch (CompletionException failed) {
// The caller has observed a live-process failure; a durable retry still needs storage.
throw failed;
}Verification boundary
AsyncFailureContractTest.failureIsObservedThroughTheReturnedFuture and OutboxWriteContractTest.receiptAndOutboxRowCommitOrRollBackTogether runs in the downloadable Spring source kit. The excerpt omits imports and surrounding setup; the kit contains the complete tests.
Costs and limits
A future holds completion state in memory. A database outbox adds write I/O, storage and worker scans. The two local tests verify a failure result and an atomic H2 write separately; they do not implement an end-to-end delivery service.
Common Mistakes
- Do not return a durable-acceptance response for work only queued in RAM.
- Do not retry a broker send without a duplicate policy.
- Do not confuse an executor queue with an outbox table.
Read next
Spring @Async futures: make worker failure observable to the caller, Spring transactional outbox: commit a receipt and event row together, Spring outbox claims: move pending work once per database state.
Continue with checked worker recovery
Continue with Spring task executors: reject work when every slot is occupied.
