skip to content

Explain the four TransactionPhase values (BEFORE_COMMIT, AFTER_COMMIT, AFTER_ROLLBACK, AFTER_COMPLETION) and when each fires.

level: middleimportance: must knowfreq 50%

answer

  1. BEFORE_COMMIT = still in tx, atomic
  2. AFTER_COMMIT = after success (default)
  3. AFTER_ROLLBACK = on failure
  4. AFTER_COMPLETION = either way
  5. commit/rollback are refinements of completion

basics

~10 s

BEFORE_COMMIT fires just before commit (still in the tx). AFTER_COMMIT (default) fires after a successful commit. AFTER_ROLLBACK fires after a rollback. AFTER_COMPLETION fires after the tx ends either way.

solid answer

~40 s

TransactionPhase controls when the listener runs relative to the transaction. BEFORE_COMMIT executes during beforeCommit — still inside the transaction, so any DB writes join the same atomic commit. AFTER_COMMIT (the default) executes only after a successful commit, ideal for external side effects that must not happen if the data wasn't persisted. AFTER_ROLLBACK executes after the transaction rolls back, useful for compensation or cleanup. AFTER_COMPLETION executes after the transaction finishes regardless of the outcome — commit or rollback — good for unconditional resource cleanup. AFTER_COMMIT and AFTER_ROLLBACK are mutually exclusive specializations of AFTER_COMPLETION; you'd only get one of them per transaction. A single listener declares exactly one phase.

code

java · 23 lines
java
@Component
public class AuditHandlers {

    @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
    public void validateBeforeCommit(OrderEvent e) {
        // still inside the tx — throwing here aborts the commit
    }

    @TransactionalEventListener // default AFTER_COMMIT
    public void publishAfterCommit(OrderEvent e) {
        messageBroker.publish(e); // only if committed
    }

    @TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
    public void onRollback(OrderEvent e) {
        alerting.notifyFailure(e);
    }

    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMPLETION)
    public void cleanup(OrderEvent e) {
        contextHolder.clear(); // runs whether committed or rolled back
    }
}

go deeper

for a junior

Name the four phases and roughly when each fires.

for a middle

Explain that BEFORE_COMMIT is inside the tx and after-* phases cannot undo the outcome.

for a senior

Map each phase to TransactionSynchronization callbacks and discuss exception behavior per phase.

for a principal

Reason about atomicity boundaries and when to choose BEFORE_COMMIT vs AFTER_COMMIT for consistency guarantees.

## The TransactionPhase enum `org.springframework.transaction.event.TransactionPhase` has four values, each mapping onto a callback of Spring's `TransactionSynchronization` interface. ### BEFORE_COMMIT - Fires during `TransactionSynchronization.beforeCommit(readOnly)`, **just before** the actual commit. - The transaction is **still active**, so persistence operations you perform here are flushed and committed as part of the **same** transaction — they are atomic with the original work. - If the handler throws, it can still **abort the commit** (the exception propagates and triggers rollback). - Use for: last-minute validation, deriving/persisting additional data that must commit-or-rollback together. ### AFTER_COMMIT (default) - Fires during `afterCompletion(STATUS_COMMITTED)`, **after** a successful commit. - The original transaction is **already committed**. New DB work here is **not** part of it; to persist you must open a new transaction (`@Transactional(propagation = REQUIRES_NEW)`), otherwise writes may be silently discarded. - If the handler throws, the commit has **already happened** and cannot be undone — the exception is logged but does not roll anything back. - Use for: external side effects (emails, message-broker publishing, cache eviction) that should only occur if data was durably saved. This underpins the transactional-outbox / reliable-side-effect pattern. ### AFTER_ROLLBACK - Fires during `afterCompletion(STATUS_ROLLED_BACK)`, after the transaction rolled back. - Use for: compensation, alerting, cleanup that is specific to failure. - Note: exceptions thrown in after-completion callbacks are logged and do not change the (already-decided) outcome. ### AFTER_COMPLETION - Fires during `afterCompletion(status)` for **any** completion status — both commit and rollback. - Use for: unconditional cleanup (release resources, clear a thread-local, metrics) that must run either way. ## Relationship between phases `AFTER_COMMIT` and `AFTER_ROLLBACK` are **refinements** of `AFTER_COMPLETION` — for a given transaction exactly one of commit/rollback occurs, so an AFTER_COMMIT listener and an AFTER_ROLLBACK listener are mutually exclusive for that transaction, while an AFTER_COMPLETION listener runs regardless. ## Ordering & multiple listeners Multiple transactional listeners can be ordered with `@Order`. All BEFORE_COMMIT listeners run before the commit; all after-completion listeners run afterward. ## Gotchas - Doing DB writes in AFTER_COMMIT without REQUIRES_NEW is the single most common bug — silently lost writes. - Throwing in BEFORE_COMMIT rolls the whole thing back; throwing in after-* phases only logs. - Each listener has exactly one phase; you can't cover both commit and rollback with a single AFTER_COMMIT/AFTER_ROLLBACK listener — use AFTER_COMPLETION or two listeners.

  • If both an AFTER_COMMIT and an AFTER_COMPLETION listener exist, which run on a successful commit?
    Both — AFTER_COMPLETION always runs, and AFTER_COMMIT runs because the commit succeeded. On a rollback, AFTER_COMPLETION and AFTER_ROLLBACK would run instead.
  • What happens if a BEFORE_COMMIT listener throws an exception?
    The exception propagates and prevents the commit, triggering a rollback — because the code is still executing inside the active transaction.

saying these in an interview costs you the question

  • Claiming AFTER_COMMIT can abort/rollback the transaction
  • Saying AFTER_COMPLETION only runs on commit
  • Thinking BEFORE_COMMIT writes are separate from the main transaction

context