A TransactionalEventListener can defer an event callback until a selected transaction phase, such as AFTER_COMMIT.
Spring transaction events: run a listener after commit without claiming durability
The downloadable Spring source kit pins Java 21 and Spring Boot 4.0.8 with its managed dependencies. Run mvn test to check the named fixture.
Publish in the transaction you mean
The event fixture publishes an imported receipt three ways: outside a transaction, inside a rolled-back transaction, and inside a committed transaction. With default fallback behavior, only the committed event reaches the AFTER_COMMIT list. The test checks the final list contains the third receipt alone.
The list is ordinary process-local state and the listener runs in the test thread. This makes the phase contract inspectable. It does not provide a queue, durable log, separate worker or cross-process delivery mechanism. Ordinary application events and transaction-bound listeners have different timing rules.
A completed commit cannot be undone by the listener
When the listener starts after commit, the original database transaction has already succeeded. A listener failure must be handled as a post-commit failure; presenting it as though the original insert rolled back would lie to the caller. A fresh database write from this phase needs its own explicit transaction design.
A process can stop after committing a row and before delivering an in-memory callback. An outbox design addresses that gap by writing delivery intent with the business transaction and processing it separately. This lesson identifies the gap; it does not ship a durable outbox or an exactly-once consumer.
Checked source
package in.aitrove.contracts;
import java.util.ArrayList;
import java.util.List;
import org.springframework.transaction.event.*;
public class CommitEvents {
public record ImportedReceipt(int id) {}
public final List<Integer> committed=new ArrayList<>();
@TransactionalEventListener(phase=TransactionPhase.AFTER_COMMIT)
public void committed(ImportedReceipt event) { committed.add(event.id()); }
}Test the boundary
FrameworkBoundaryTest.afterCommitDoesNotRunOnRollbackOrWithoutTransaction checks this contract in the source kit. Excerpts belong to the named classes; use the downloadable files for imports, configuration and assertions.
Costs and boundaries
The callback list grows with observed events and is unbounded in this teaching fixture. A production listener needs retention, concurrency and failure policies. AFTER_COMMIT describes timing; it supplies neither durable storage nor a latency limit.
Common Mistakes
- Do not treat an in-memory callback as durable message delivery.
- Do not assume a listener failure can roll back an already committed transaction.
- Do not rely on default delivery outside a transaction.
Read next
Spring application events: local delivery is not a durable queue, Spring transaction propagation: joined rollback and independent commit, Spring task executors: capacity, rejection and lost context, Java JDBC transactions: atomic updates and rollback.
Continue with Spring delivery contracts
Continue with Spring transactional outbox: commit a receipt and event row together.
