Name the TransactionSynchronization callbacks and describe the exact order they fire in on a successful commit versus on a rollback.
answer
- beforeCommit → beforeCompletion → COMMIT → afterCommit → afterCompletion
- rollback skips beforeCommit AND afterCommit
- beforeCompletion/afterCompletion run either way
- status: COMMITTED / ROLLED_BACK / UNKNOWN
- beforeCommit throws → beforeCompletion still runs
basics
~10 sCommit path: beforeCommit, then beforeCompletion, then the physical commit, then afterCommit, then afterCompletion(STATUS_COMMITTED). Rollback path: beforeCommit is skipped; beforeCompletion runs, then rollback, then afterCompletion(STATUS_ROLLED_BACK). afterCommit never runs on rollback.
solid answer
~40 sThere are four callbacks. On a successful commit the order is: beforeCommit(readOnly) → beforeCompletion() → [physical commit] → afterCommit() → afterCompletion(STATUS_COMMITTED). On a rollback, beforeCommit is not called at all; beforeCompletion() still runs, then the physical rollback, then afterCompletion(STATUS_ROLLED_BACK). So afterCommit is commit-only, while beforeCompletion and afterCompletion always run regardless of outcome — that makes beforeCompletion the place for unconditional resource cleanup and afterCompletion the place to react to the final status. One subtlety: if beforeCommit throws, Spring still calls beforeCompletion (with the transaction now doomed to roll back) before rolling back, so cleanup is guaranteed. afterCompletion's status can also be STATUS_UNKNOWN if the commit outcome could not be determined.
code
java · 12 linesTransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override public void beforeCommit(boolean readOnly) { /* commit path only */ }
@Override public void beforeCompletion() { /* commit AND rollback */ }
@Override public void afterCommit() { /* commit path only, already durable */ }
@Override public void afterCompletion(int status) {
switch (status) {
case TransactionSynchronization.STATUS_COMMITTED -> {/* success */}
case TransactionSynchronization.STATUS_ROLLED_BACK -> {/* undo */}
default /* STATUS_UNKNOWN */ -> {/* ambiguous */}
}
}
});go deeper
Know that afterCommit is commit-only and afterCompletion runs either way with a status.
Recite both orderings precisely and know which callbacks are unconditional.
Explain the beforeCommit-throws path and STATUS_UNKNOWN, and map phases to @TransactionalEventListener.
Reason about ordering guarantees across multiple synchronizations and resource-cleanup timing between phases.
## The four callbacks and their semantics 1. **`beforeCommit(boolean readOnly)`** — invoked before the physical commit, while the transaction is still open. Commit-path only. The `readOnly` flag reflects the transaction's read-only attribute. 2. **`beforeCompletion()`** — invoked before the transaction completes, on **both** commit and rollback. Intended for resource cleanup (closing/releasing things bound to the transaction). 3. **`afterCommit()`** — invoked only after a **successful** physical commit. Never called on rollback. 4. **`afterCompletion(int status)`** — invoked after completion on **both** paths. `status` is one of `STATUS_COMMITTED (0)`, `STATUS_ROLLED_BACK (1)`, or `STATUS_UNKNOWN (2)`. ## Commit ordering (happy path) ``` beforeCommit(readOnly) beforeCompletion() <physical COMMIT> afterCommit() afterCompletion(STATUS_COMMITTED) ``` Note that `afterCommit` and `afterCompletion` both run **after** the resource has committed. Between `beforeCompletion` and `afterCommit`, Spring also unbinds/cleans the transactional resources from the thread. ## Rollback ordering ``` (beforeCommit is SKIPPED) beforeCompletion() <physical ROLLBACK> afterCompletion(STATUS_ROLLED_BACK) ``` `afterCommit` is absent. `beforeCompletion` and `afterCompletion` still fire, which is why they are the reliable hooks for 'always do this' logic. ## The beforeCommit-throws case If `beforeCommit` throws an exception, the transaction is marked for rollback. Spring's `AbstractPlatformTransactionManager.processCommit` still invokes `beforeCompletion` (it tracks a `beforeCompletionInvoked` flag) before rolling back, then calls `afterCompletion(STATUS_ROLLED_BACK)`. So even a failure in `beforeCommit` gives you a `beforeCompletion` cleanup pass. Net effective order in that case: ``` beforeCommit() -> throws beforeCompletion() <ROLLBACK> afterCompletion(STATUS_ROLLED_BACK) ``` ## STATUS_UNKNOWN If the transaction manager cannot determine the commit outcome (e.g. a heuristic or an error during commit whose result is ambiguous), `afterCompletion` receives `STATUS_UNKNOWN`. Defensive callback code should not assume only committed/rolled-back. ## Multiple synchronizations When several synchronizations are registered, each phase iterates over all of them (sorted by order) before moving to the next phase. So every registered `beforeCommit` runs, then every `beforeCompletion`, etc. ## Mapping to @TransactionalEventListener phases - `BEFORE_COMMIT` → `beforeCommit` - `AFTER_COMMIT` (default) → `afterCommit` - `AFTER_ROLLBACK` → `afterCompletion` filtered to STATUS_ROLLED_BACK - `AFTER_COMPLETION` → `afterCompletion` (any status) ## Practical guidance - Durable, commit-only side effects → `afterCommit`. - Unconditional cleanup that must happen either way → `beforeCompletion` (still inside completion) or `afterCompletion`. - React to whether it committed or rolled back → `afterCompletion(status)` and inspect the status.
- Which callbacks are guaranteed to run whether the transaction commits or rolls back?beforeCompletion() and afterCompletion(int). beforeCommit and afterCommit are commit-path only.
- If beforeCommit throws, does beforeCompletion still get called?Yes. Spring marks the transaction for rollback but still invokes beforeCompletion before rolling back, then afterCompletion(STATUS_ROLLED_BACK).
saying these in an interview costs you the question
- Claiming afterCommit runs on rollback
- Claiming beforeCompletion only runs on commit
- Forgetting STATUS_UNKNOWN is a possible afterCompletion status
- Thinking beforeCommit runs after the physical commit