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

Spring async event listener: handle detached failures and queue pressure

Last updated: 5 Oct 20264 min read
tutorial
IntermediateBy AITrove Editorial

An @Async event listener releases the publisher quickly, but its exception, executor capacity and transaction context need separate handling.

The caller no longer owns completion

An @Async @EventListener runs on an executor when async support is enabled and the listener is invoked through the Spring-managed path. A void listener's exception does not propagate to the event publisher. Configure error reporting for that executor path, and treat a rejected submission as a distinct overload condition. Synchronous listeners have the opposite failure relationship; choose deliberately rather than adding @Async to silence a slow request.

Pass stable data

The worker thread does not inherit the publisher's transaction. Passing a managed entity that expects an open persistence context can fail or read unexpected state. Publish IDs and immutable values, then load the required state in the worker's own transaction if needed. Even after-commit delivery remains in-memory; a crash can erase accepted work. For a receipt that must reach another system, durable delivery and an outbox are the relevant boundary.

Prove error visibility

Force the listener's target to throw and assert an observable failure record or error-handler call while the publisher's result remains as designed. Fill the executor queue and test whether the event is rejected, blocked or sent to another path. Use a fixed clock and controlled executor in tests so completion timing is deterministic. A log line from a worker is insufficient proof of delivery to a remote recipient.

Implementation contract

Java
@Component
final class ReceiptSearchListener {
    private final ReceiptSearchIndex searchIndex;

    ReceiptSearchListener(ReceiptSearchIndex searchIndex) {
        this.searchIndex = searchIndex;
    }

    @Async("receiptEventExecutor")
    @EventListener
    void on(ReceiptCommitted committed) {
        searchIndex.refresh(committed.receiptId());
    }
}

Cost and verification

The publisher saves listener latency, but each pending event consumes queue space and may become stale. A bounded executor makes overload explicit; an outbox adds storage and replay cost when completion must survive process failure.

Common Mistakes

  • Do not expect a void async listener exception to reach publishEvent.
  • Do not pass a lazy JPA entity to a detached worker thread.
  • Do not call an in-memory async listener a durable integration channel.

Read next

Spring synchronous event listener failure: publication can fail the caller, Spring async work versus durable delivery: separate latency from recovery, Spring task executors: reject work when every slot is occupied, Spring imperative transaction: a worker thread does not inherit it, Spring transactional outbox: commit a receipt and event row together.

spring
spring-boot
spring-core
async-event-listener-failure
Storage details