Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Spring transaction events: run a listener after commit without claiming durability

Last updated: 30 Sept 20264 min read
tutorial
IntermediateBy AITrove Editorial

A TransactionalEventListener can defer an event callback until a selected transaction phase, such as AFTER_COMMIT.

Download Spring source kit

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

Java
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.

spring
spring-boot
transaction-events
Storage details