Using Spring Transaction Synchronization Hooks to Send Kafka Messages After Commit

This article explains how to use Spring's TransactionSynchronizationManager to detect active transactions and register synchronization callbacks that asynchronously send Kafka messages only after a transaction commits, illustrated with a payment‑system case and complete Java code examples.

Top Architect
Top Architect
Top Architect
Using Spring Transaction Synchronization Hooks to Send Kafka Messages After Commit

Preface

Today I want to share a little‑known technique: the hook functions of Spring transactions. To avoid a dry technical lecture, I will illustrate the correct usage with a concrete case.

Case Background

In a payment system we need to record each account's fund flow. The CTO requires that every write of a flow be pushed as a message to Kafka, and a separate archiving service consumes the message and persists it to a database that only the archiving service can write.

The process is simple: the payment service sends a Kafka message for each flow, and the archiving service stores it.

To avoid coupling with the integrator’s KafkaTemplate, the team decides to develop a second‑party library that provides a starter‑style API for sending messages to Kafka, supports transaction synchronization, and minimizes integration effort.

Solution

The key requirement is that sending the message must be transactional and must not affect the main business logic. The library should detect whether a transaction is active; if not, send the message asynchronously immediately; if a transaction is active, register a synchronization callback that sends the message after the transaction commits.

TransactionSynchronizationManager in Action

TransactionSynchronizationManager is a static utility class that holds a ThreadLocal set of TransactionSynchronization objects. The following pseudo‑code demonstrates how to use it:

private final ExecutorService executor = Executors.newSingleThreadExecutor();

public void sendLog() {
    // Check whether a transaction is active
    if (!TransactionSynchronizationManager.isSynchronizationActive()) {
        // No transaction – send the message asynchronously
        executor.submit(() -> {
            try {
                // send message to Kafka
            } catch (Exception e) {
                // log exception, notify developers
            }
        });
        return;
    }

    // Transaction is active – register a synchronization callback
    TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
        @Override
        public void afterCompletion(int status) {
            if (status == TransactionSynchronization.STATUS_COMMITTED) {
                // After commit, send the message asynchronously
                executor.submit(() -> {
                    try {
                        // send message to Kafka
                    } catch (Exception e) {
                        // log exception
                    }
                });
            }
        }
    });
}

The method isSynchronizationActive() returns true when a transaction has been started and the thread‑local variable has been populated by Spring’s transaction manager. The method registerSynchronization(...) adds a custom TransactionSynchronizationAdapter to the thread‑local set, allowing the library to execute custom logic after the transaction completes.

How to Determine Transaction Presence

The source code of TransactionSynchronizationManager.isSynchronizationActive() simply checks whether the internal ThreadLocal set is non‑null.

Spring activates the synchronization in TransactionSynchronizationManager.initSynchronization(), which is called by the transaction manager when a transaction begins.

How to Trigger Custom Logic After Commit

By registering a TransactionSynchronizationAdapter and overriding afterCompletion(int status), the library can run code only when status == TransactionSynchronization.STATUS_COMMITTED, i.e., after a successful commit.

Conclusion

When using TransactionSynchronizationManager, be careful not to switch threads between the transaction and the callback, otherwise the thread‑local data will be lost and the hook will not fire.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavatransactionspringKafkaSynchronization
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.