Backend Development 12 min read

Using Spring Transaction Hooks to Send Kafka Messages After Commit

This article explains how to leverage Spring's TransactionSynchronizationManager to detect active transactions, register synchronization callbacks, and asynchronously send Kafka messages only after a transaction successfully commits, providing a practical example and detailed code snippets for building a reusable Spring Boot starter library.

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

In this technical guide, a senior architect shares a practical solution for integrating Kafka message publishing with Spring transaction management, ensuring that messages are only sent after a transaction commits.

1. Case Background

The payment system needs to record account fund flows and archive them by pushing related information to Kafka, where a separate archiving service consumes the messages and writes them to a database with exclusive write permissions.

2. Solution Design

To avoid interfering with the main business logic, a second‑party library is created as a Spring Boot starter that handles Kafka production internally, without using Spring's KafkaTemplate to prevent conflicts with existing integrations.

Key Requirements

Provide a starter‑style library for Spring Boot.

Use the native Kafka producer API inside the library.

Offer a simple API that is easy to adopt.

Support transaction semantics so that message sending does not affect the primary business flow.

3. TransactionSynchronizationManager in Action

The core of the solution relies on TransactionSynchronizationManager , a Spring utility that tracks transaction state via a thread‑local variable.

3.1 Detecting an Active Transaction

The static method TransactionSynchronizationManager.isSynchronizationActive() returns true when a transaction is currently active because the thread‑local synchronizations set has been initialized by the transaction manager.

public static boolean isSynchronizationActive() {
    return (synchronizations.get() != null);
}

3.2 Registering a Callback for After‑Commit Logic

When a transaction is active, we register a TransactionSynchronizationAdapter that overrides afterCompletion(int status) . If the status equals TransactionSynchronization.STATUS_COMMITTED , the callback asynchronously sends the Kafka message.

TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
    @Override
    public void afterCompletion(int status) {
        if (status == TransactionSynchronization.STATUS_COMMITTED) {
            executor.submit(() -> {
                try {
                    // send message to Kafka
                } catch (Exception e) {
                    // log or handle exception
                }
            });
        }
    }
});

3.3 Full Pseudo‑Code Example

private final ExecutorService executor = Executors.newSingleThreadExecutor();

public void sendLog() {
    // Check if a transaction is active
    if (!TransactionSynchronizationManager.isSynchronizationActive()) {
        // No transaction: send message immediately (asynchronously)
        executor.submit(() -> {
            try {
                // send message to Kafka
            } catch (Exception e) {
                // handle exception
            }
        });
        return;
    }
    // Transaction is active: register a synchronization callback
    TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
        @Override
        public void afterCompletion(int status) {
            if (status == TransactionSynchronization.STATUS_COMMITTED) {
                executor.submit(() -> {
                    try {
                        // send message to Kafka
                    } catch (Exception e) {
                        // handle exception
                    }
                });
            }
        }
    });
}

The callback is stored in the same thread‑local set, so Spring will invoke it during the transaction lifecycle (e.g., invokeAfterCommit and invokeAfterCompletion ), guaranteeing that the message is only published after a successful commit.

4. Important Considerations

Because the transaction state is kept in a thread‑local variable, the library must avoid switching threads between the transaction and the callback; otherwise, the synchronization will not be detected and the hook will not fire.

5. Conclusion

By using TransactionSynchronizationManager to check transaction activity and register post‑commit callbacks, developers can safely integrate Kafka message production into Spring services without impacting the primary business logic, and the solution can be packaged as a reusable Spring Boot starter.

BackendJavatransactionSpringKafkaSpringBootTransactionSynchronizationManager
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

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