Default application event delivery runs listeners in the publisher's thread, so listener latency and exceptions become part of the caller's result.
Spring synchronous event listener failure: publication can fail the caller
Publication is usually a method call
A receipt service publishes ReceiptAccepted while saving a row. With the default multicaster, listeners run synchronously on that thread. A listener that throws can stop publication and propagate its exception to the service; if the service is still inside a transaction, that may roll back the save. An event name does not make the work asynchronous or durable. Application events are local in-process communication, and transaction phase listeners move selected work to a different commit point.
Define the failure owner
A required invariant, such as updating a same-database receipt index, can legitimately fail the command. A best-effort notification should not decide whether the receipt is accepted. Move the latter after commit and give it a retryable durable channel if losing it matters. Configuring a multicaster ErrorHandler to swallow every listener failure can make a broken required listener invisible; assign failure handling per use case rather than globally masking exceptions.
Exercise rollback and ordering
Register a listener that records a call, then one that throws. Publish inside a transaction and assert the command's database state matches the intended failure contract. Do not depend on an incidental listener iteration order; if order matters, declare it and test it, or call a service directly when the steps form one business operation. Measure listener duration because synchronous listeners extend request latency and hold the publisher's resources.
Implementation contract
@Component
final class ReceiptIndexListener {
private final ReceiptIndex receiptIndex;
ReceiptIndexListener(ReceiptIndex receiptIndex) {
this.receiptIndex = receiptIndex;
}
@EventListener
void on(ReceiptAccepted accepted) {
receiptIndex.record(accepted.receiptId());
}
}Cost and verification
Each synchronous listener adds its execution time to publishEvent and may hold the caller's transaction open. A slow remote call inside a listener can extend lock time and cause retries elsewhere.
Common Mistakes
- Do not assume @EventListener makes delivery asynchronous or durable.
- Do not swallow every listener error when some listeners enforce required state.
- Do not call a remote service inside a transaction-scoped listener without a clear failure budget.
Read next
Spring application events: local delivery is not a durable queue, Spring transaction events: run a listener after commit without claiming durability, Spring async event listener: handle detached failures and queue pressure, Spring transactional outbox: commit a receipt and event row together, Spring transactional event test: cross an actual commit boundary.
