What is a TransactionSynchronization, and why would you use its afterCommit callback instead of just putting the code at the end of a @Transactional method?
answer
- afterCommit = data already durable
- method body runs BEFORE physical commit
- registerSynchronization needs active tx
- @TransactionalEventListener = declarative wrapper
- avoid email-then-rollback bug
basics
~20 sA TransactionSynchronization is a hook Spring calls at points in a transaction's life. Its afterCommit runs only after the transaction actually commits — so side effects like sending an email won't happen if the transaction rolls back.
solid answer
~40 sTransactionSynchronization is a Spring interface with lifecycle callbacks (beforeCommit, beforeCompletion, afterCommit, afterCompletion) that fire while Spring drives a transaction to completion. You register one via TransactionSynchronizationManager.registerSynchronization. The key win over 'just call it at the end of the method' is timing: code at the end of a @Transactional method still runs before the physical commit, so if the commit later fails and rolls back, you have already fired the side effect (email, message, cache eviction). afterCommit only runs after a successful commit, guaranteeing the DB change is durable before you act on it. In modern Spring you rarely implement the interface by hand — @TransactionalEventListener(phase = AFTER_COMMIT) is the declarative front end built on exactly this mechanism.
code
java · 17 lines@Service
public class OrderService {
@Transactional
public void placeOrder(Order order) {
orderRepo.save(order);
// Defer the email until the commit actually succeeds.
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronization() {
@Override
public void afterCommit() {
emailService.sendConfirmation(order);
}
});
}
}go deeper
Know that afterCommit runs after a successful commit and is the safe place for side effects like emails.
Explain why the method body runs before commit and how registerSynchronization / @TransactionalEventListener solve it.
Discuss durability guarantees, when to use the raw interface vs. events, and rollback handling.
Frame it as the reliability primitive behind event-driven side effects and the outbox pattern; know its limits (no XA guarantee that the side effect itself succeeds).
## What it is `org.springframework.transaction.support.TransactionSynchronization` is an interface with a set of callback methods that Spring's transaction infrastructure invokes as a transaction moves through its lifecycle. You attach a synchronization to the **currently active transaction** by calling `TransactionSynchronizationManager.registerSynchronization(mySync)`. From then on Spring will call your methods at the right moments (before commit, after commit, on completion, etc.). ## The four main callbacks - **`beforeCommit(boolean readOnly)`** — runs just before the physical commit, *still inside the transaction*. You can still touch the database here (e.g. flush a last change). - **`beforeCompletion()`** — runs right before the transaction completes (whether it will commit or roll back). Meant for resource cleanup. - **`afterCommit()`** — runs *after* a successful commit. The data is already durable. - **`afterCompletion(int status)`** — runs after the transaction finishes either way; `status` tells you whether it committed or rolled back. ## Why afterCommit beats 'code at the end of the method' Consider: ```java @Transactional public void placeOrder(Order o) { orderRepo.save(o); emailService.sendConfirmation(o); // runs BEFORE commit! } ``` The method body executes entirely *before* Spring commits. The commit happens when the proxy that wraps the method returns. So `sendConfirmation` fires while the transaction is still open. If the commit then fails (constraint violation, DB down, optimistic-lock conflict on flush), the order is **not** saved but the customer already got a 'your order is confirmed' email — a real bug. Moving the email into `afterCommit` guarantees it only fires once the order is truly persisted: ```java TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { @Override public void afterCommit() { emailService.sendConfirmation(o); } }); ``` ## The modern, declarative way Hand-writing synchronizations is verbose. Spring gives you `@TransactionalEventListener`, which registers a synchronization for you under the hood: ```java @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) public void on(OrderPlaced e) { emailService.sendConfirmation(e.order()); } ``` You publish an `OrderPlaced` event inside the transaction; the listener runs after commit. Same guarantee, far less boilerplate. ## When to reach for the raw interface Use `TransactionSynchronization` directly when you need fine control (custom ordering, resource cleanup in `beforeCompletion`, reacting to rollback via `afterCompletion`) or when you are writing infrastructure code rather than domain logic. For ordinary 'do X after commit' domain needs, prefer `@TransactionalEventListener`. ## Gotcha Registration only works when a Spring-managed transaction with active synchronization exists on the thread; otherwise `registerSynchronization` throws `IllegalStateException`. So this only fires inside a `@Transactional` boundary.
- What is the declarative alternative to writing a synchronization by hand?@TransactionalEventListener, e.g. phase = AFTER_COMMIT. Spring registers a TransactionSynchronization for you and drives it from the published event.
- What happens if you call registerSynchronization outside any transaction?It throws IllegalStateException ('Transaction synchronization is not active'). A synchronization can only bind to an active Spring-managed transaction on the current thread.
saying these in an interview costs you the question
- Thinking code at the end of a @Transactional method runs after the commit
- Believing afterCommit can still roll the transaction back
- Assuming you can register a synchronization anywhere, without a transaction