skip to content

What is @TransactionalEventListener and what is its default phase?

level: juniorimportance: must knowfreq 55%

answer

  1. specialization of @EventListener
  2. default = AFTER_COMMIT
  3. ties handler to tx lifecycle
  4. no tx + no fallback = skipped
  5. registers TransactionSynchronization

basics

~10 s

It's a Spring event listener that only fires relative to a transaction. By default it runs in the AFTER_COMMIT phase, so it executes only after the surrounding transaction successfully commits.

solid answer

~30 s

@TransactionalEventListener is a specialization of @EventListener that binds the listener's execution to the lifecycle of the transaction that was active when the event was published. Instead of running synchronously at publish time (like @EventListener), it registers a transaction synchronization callback and runs at a chosen phase. The default phase is TransactionPhase.AFTER_COMMIT, meaning the handler runs only after the publishing transaction commits successfully. This is the common pattern for 'do side effects only if the data was actually persisted' — sending emails, publishing to a message broker, etc. If no transaction is active, by default the listener is silently skipped unless fallbackExecution=true.

code

java · 18 lines
java
@Component
public class OrderNotifier {

    // Default phase is AFTER_COMMIT: fires only after a successful commit
    @TransactionalEventListener
    public void onOrderPlaced(OrderPlacedEvent event) {
        // safe to send an email — the order row is durably committed
        emailService.sendConfirmation(event.orderId());
    }
}

// Publisher side (inside a @Transactional method)
@Transactional
public void placeOrder(Order order) {
    orderRepository.save(order);
    eventPublisher.publishEvent(new OrderPlacedEvent(order.getId()));
    // listener runs after this method's tx commits
}

go deeper

for a junior

Know it's @EventListener bound to a transaction and the default is AFTER_COMMIT.

for a middle

Explain the no-transaction skip behavior and fallbackExecution.

for a senior

Discuss the TransactionSynchronization mechanism and why AFTER_COMMIT is the safe default for side effects.

for a principal

Frame it as a durability boundary for outbox/side-effect patterns and reason about failure modes when no tx is present.

## What it is `@TransactionalEventListener` (package `org.springframework.transaction.event`) is a specialized form of `@EventListener`. A normal `@EventListener` handles an application event **synchronously at the moment `ApplicationEventPublisher.publishEvent(...)` is called** — regardless of any transaction. `@TransactionalEventListener` instead **defers** the handler and ties it to the **current transaction's lifecycle**, so it fires at a specific point relative to commit/rollback. ## How it works under the hood When an event is published inside an active transaction, Spring registers a `TransactionSynchronization` callback (via `TransactionSynchronizationManager`). That callback fires the listener during the transaction's commit/completion sequence, at the configured `TransactionPhase`. ## The `phase` attribute (TransactionPhase enum) - `BEFORE_COMMIT` — runs just before the transaction commits (during `beforeCommit`). Still inside the transaction, so DB writes here participate in the **same** commit. - `AFTER_COMMIT` — **the default**. Runs after a successful commit. The transaction is already committed; new DB work here needs a new transaction. - `AFTER_ROLLBACK` — runs after the transaction rolls back. - `AFTER_COMPLETION` — runs after the transaction completes, **regardless** of commit or rollback outcome. (`AFTER_COMMIT` and `AFTER_ROLLBACK` are mutually exclusive refinements of `AFTER_COMPLETION`.) ## fallbackExecution By default, if there is **no active transaction** when the event is published, the listener does **not** run at all (Spring logs at trace/debug that no transaction synchronization is active). Setting `fallbackExecution = true` makes the listener run immediately, like a plain `@EventListener`, when no transaction is present. ## When to use which phase - `AFTER_COMMIT` (default): trigger external side effects only if the data was durably persisted — send emails, enqueue messages, invalidate caches. Most common. - `BEFORE_COMMIT`: do additional work that must be part of the same atomic commit (extra validation, additional persistence that should roll back together). - `AFTER_ROLLBACK`: compensation/cleanup after failure. - `AFTER_COMPLETION`: unconditional cleanup regardless of outcome. ## Key gotchas - The default `AFTER_COMMIT` handler runs **after** commit, so any JPA/DB operations you perform there are **not** part of the committed transaction. If you write to the DB in an AFTER_COMMIT handler using the same (now read-only-ish) context, it may be silently lost unless you open a **new** transaction (e.g., `@Transactional(propagation = REQUIRES_NEW)`). - Without an active transaction and without `fallbackExecution=true`, your handler **silently does nothing** — a classic 'my listener never fires' bug. - The event must be published **while a transaction is active** for the deferral to work.

  • How does @TransactionalEventListener differ from @EventListener?
    @EventListener runs synchronously at publishEvent() time regardless of any transaction. @TransactionalEventListener defers execution and binds it to the current transaction's phase (default AFTER_COMMIT), and by default is skipped entirely if no transaction is active.
  • What happens if you publish the event outside a transaction?
    By default the listener is not invoked at all. You must set fallbackExecution=true for it to run immediately like a normal @EventListener.

saying these in an interview costs you the question

  • Thinking the default phase is BEFORE_COMMIT
  • Believing the listener always runs even without a transaction
  • Confusing it with @EventListener (synchronous at publish time)

context