Mastering Spring Transaction Hooks: Send Kafka Messages Only After Commit

This article explains how to use Spring's TransactionSynchronizationManager to detect active transactions and register callbacks that asynchronously send Kafka messages after a transaction successfully commits, ensuring data consistency in payment‑system archiving scenarios.

Architect
Architect
Architect
Mastering Spring Transaction Hooks: Send Kafka Messages Only After Commit

After previous summaries of Spring AOP and transactions, the article introduces Spring transaction hook functions to handle a payment‑system scenario where each fund flow must be archived by sending a message to Kafka after the transaction commits.

Case Background

In a payment system, every account's fund flow needs to be recorded. The CTO requires that each flow be archived by pushing a message to Kafka, which a separate archiving service consumes and stores in a database that only the archiving service can write to.

To support multiple business units, the team decides to develop a secondary library (a Spring Boot starter) that sends messages to Kafka, avoiding conflicts with existing KafkaTemplate usage.

Design Considerations

Provide the library as a Spring Boot starter.

Implement the Kafka producer directly instead of using Spring's KafkaTemplate to prevent integration conflicts.

Expose a simple API so business code can adopt it with minimal learning cost.

Ensure message sending is transactional and does not affect the main business flow.

Transaction Hook Implementation

The core solution uses TransactionSynchronizationManager to determine whether a transaction is active and to register a synchronization callback that runs after the transaction commits.

private final ExecutorService executor = Executors.newSingleThreadExecutor();

public void sendLog() {
    // Check if a transaction is active
    if (!TransactionSynchronizationManager.isSynchronizationActive()) {
        // No transaction: send message asynchronously
        executor.submit(() -> {
            try {
                // send to Kafka
            } catch (Exception e) {
                // log error
            }
        });
        return;
    }

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

The method TransactionSynchronizationManager.isSynchronizationActive() checks a thread‑local ThreadLocal<Set<TransactionSynchronization>> that Spring populates when a transaction begins via initSynchronization(). If it returns true, a transaction is in progress.

To execute custom logic after commit, the code registers a TransactionSynchronizationAdapter via registerSynchronization(). Spring invokes afterCompletion with the transaction status, allowing the message to be sent only when the transaction is successfully committed.

Important Notes

The synchronization relies on thread‑local storage; switching threads before the callback runs will cause the hook to be ineffective.

Asynchronous execution ensures the main business flow is not blocked.

Both detection and callback registration are encapsulated inside the secondary library, keeping integration simple for consumers.

Conclusion

By leveraging TransactionSynchronizationManager, developers can safely send Kafka messages only after a Spring transaction commits, preserving data consistency without impacting primary business logic. The approach must be used within the same thread context to avoid missed callbacks.

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.

BackendjavatransactionspringKafkaSpringBootTransactionSynchronizationManager
Architect
Written by

Architect

Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and learning.

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.