Under The Hood
backend springboot Spring Framework 6.x

@EventListener, @TransactionalEventListener, ApplicationEventPublisher

Last updated
Prerequisites:
Spring beans and DI
Understanding of transactions and loose coupling
  • spring
  • events
  • event-listener
  • transactional-event-listener
  • event-publisher
  • async

Read at your depth

The practical view

Spring events decouple components: publish with ApplicationEventPublisher.publishEvent(new OrderPlacedEvent(order)); listen with @EventListener on a method: @EventListener public void on(OrderPlacedEvent e) { ... }. The listener runs synchronously by default in the publisher's thread. For async, add @Async to the listener (and @EnableAsync). @TransactionalEventListener(phase = AFTER_COMMIT) runs the listener only after the transaction commits — perfect for sending emails/notifications that should not happen if the transaction rolls back. @TransactionalEventListener has a fallbackExecution attribute to also run when there is no transaction.

The same idea in other frameworks

react equivalent
N/A — server-side Spring concern.

Legacy vs modern

Direct service-to-service calls vs events

Calling another service directly couples the components and runs regardless of transaction outcome; events decouple them and can wait for commit.

before → after
Direct call in the same transaction
public void placeOrder(Order o) {
  orderRepo.save(o);
  emailService.sendConfirmation(o); // ❌ OLD: coupled + sent before commit
}
Publish event + transactional listener
publisher.publishEvent(new OrderPlacedEvent(o)); // ✅ NEW: decoupled

@TransactionalEventListener(phase = AFTER_COMMIT) // ✅ NEW
public void on(OrderPlacedEvent e) { emailService.sendConfirmation(e.order()); }

Interview gotchas

Context

This tests the transaction-aware event model — interviewers want the commit/rollback semantics that prevent acting on data that never materialized.

The mechanical answer

A plain @EventListener runs synchronously at publish time, which is INSIDE the transaction (if the publisher method is @Transactional). If the transaction later rolls back, the listener has already acted on data that no longer exists — an email sent for an order that was never committed. @TransactionalEventListener with phase AFTER_COMMIT registers the listener to be invoked by the transaction synchronization AFTER the commit succeeds: the listener runs only when the data is durable, and it does NOT run at all if the transaction rolls back (unless you also use AFTER_ROLLBACK or fallbackExecution). This guarantees side effects are consistent with committed state. The trade-off: the listener runs later (after commit), so it can't affect the transaction, and the commit must succeed for it to fire.

Trap

The naive answer is '@TransactionalEventListener just delays the listener'. It binds the listener to the transaction outcome — no commit, no callback (for AFTER_COMMIT). Another trap: assuming the listener shares the transaction — after commit, the transaction is gone, so the listener starts its own (or none); data read in the listener sees the committed state. Candidates who explain the synchronization binding show real Spring event mastery.

Under The Hood — a multi-depth technical interview hub.

Third-party licenses Press ⌘ K to search.