Mastering Spring Transaction Hooks: Safely Sync Kafka Messages After Commit

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

Java High-Performance Architecture
Java High-Performance Architecture
Java High-Performance Architecture
Mastering Spring Transaction Hooks: Safely Sync Kafka Messages After Commit

Introduction

After summarizing Spring AOP and transactions, the article introduces Spring transaction hook functions and demonstrates their proper usage through a real‑world case.

1. Case Background

In a payment system, each account's fund flow must be recorded and archived. The CTO requires that every flow be pushed as a message to Kafka, where an archival service consumes it and writes to a dedicated database.

The process is simple, and the team decides to build a second‑party library that encapsulates Kafka message sending.

2. Determining the Solution

Use Spring Boot and provide the library as a starter.

Implement Kafka producer directly instead of Spring’s KafkaTemplate to avoid conflicts.

Offer a simple API with low integration cost.

Ensure message sending participates in the surrounding transaction without affecting the main business.

The key challenge is to detect an active transaction and trigger the message after the transaction commits.

3. TransactionSynchronizationManager in Action

The class is a static utility that holds thread‑local transaction synchronizations. The following pseudo‑code shows how to check for an active transaction and register a synchronization that sends the Kafka message after commit.

private final ExecutorService executor = Executors.newSingleThreadExecutor();

public void sendLog() {
    // No transaction: send asynchronously
    if (!TransactionSynchronizationManager.isSynchronizationActive()) {
        executor.submit(() -> {
            // send to Kafka
        });
        return;
    }

    // Transaction active: register callback
    TransactionSynchronizationManager.registerSynchronization(
        new TransactionSynchronizationAdapter() {
            @Override
            public void afterCompletion(int status) {
                if (status == TransactionSynchronization.STATUS_COMMITTED) {
                    executor.submit(() -> {
                        // send to Kafka
                    });
                }
            }
        });
}

The method TransactionSynchronizationManager.isSynchronizationActive() checks a ThreadLocal set that is populated by TransactionSynchronizationManager.initSynchronization() when a transaction begins.

Registering a TransactionSynchronizationAdapter adds a callback to the thread‑local set, allowing Spring to invoke afterCompletion after the transaction finishes.

3.1 Detecting an Active Transaction

Source code of isSynchronizationActive() simply returns whether the thread‑local synchronizations is non‑null.

3.2 Triggering Custom Logic After Commit

By registering a synchronization, the library can execute custom code in afterCompletion, distinguishing between commit and rollback via the status parameter.

4. Summary

Both transaction detection and hook registration rely on thread‑local variables, so the library must avoid thread switching to ensure the callbacks fire correctly.

JavatransactionBackend DevelopmentSpringSpring BootTransactionSynchronizationManager
Java High-Performance Architecture
Written by

Java High-Performance Architecture

Sharing Java development articles and resources, including SSM architecture and the Spring ecosystem (Spring Boot, Spring Cloud, MyBatis, Dubbo, Docker), Zookeeper, Redis, architecture design, microservices, message queues, Git, etc.

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.