Using Spring Transaction Hooks to Send Kafka Messages After Commit
This article demonstrates how to leverage Spring's TransactionSynchronizationManager to detect active transactions and register synchronization callbacks that asynchronously push payment‑ledger messages to Kafka only after the transaction successfully commits, ensuring data consistency and minimal impact on the main business flow.
The article introduces a practical use case in a payment system where each account's fund flow must be archived by sending a message to Kafka, which is then consumed by a separate archiving service with write‑only database access.
To meet the requirement, a lightweight second‑party library (starter) is designed to encapsulate Kafka message production, avoid conflicts with existing KafkaTemplate usage, and provide a simple API that works both inside and outside of Spring transactions.
The core challenge is ensuring that message sending does not interfere with the main business logic and only occurs after the surrounding transaction commits. This is achieved by checking transaction status and registering a synchronization callback.
TransactionSynchronizationManager Overview
TransactionSynchronizationManager is a static utility class that holds a thread‑local Set<TransactionSynchronization> . When a transaction starts, Spring calls initSynchronization() , which creates the set; when the transaction ends, the registered synchronizations are invoked.
Two key methods are used:
TransactionSynchronizationManager.isSynchronizationActive() – returns true if a transaction is currently active.
TransactionSynchronizationManager.registerSynchronization(TransactionSynchronization) – adds a custom synchronization that can execute code after transaction completion.
Sample Implementation
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 message to Kafka
} catch (Exception e) {
// log/notify error
}
});
return;
}
// Transaction active: register a callback to run after commit
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/notify error
}
});
}
}
});
}The code first checks isSynchronizationActive() . If no transaction exists, it immediately sends the Kafka message asynchronously. If a transaction is present, it registers a TransactionSynchronizationAdapter whose afterCompletion method runs after the transaction finishes; only when the status indicates a commit does it send the message.
Source snippets of the relevant Spring classes are provided to illustrate how the thread‑local set is created ( initSynchronization() ) and how the active flag is evaluated.
Finally, the article warns that because the mechanism relies on thread‑local state, developers must avoid switching threads between the transaction and the callback, otherwise the synchronization will not be recognized.
Conclusion
By using TransactionSynchronizationManager to detect transaction presence and to register post‑commit callbacks, developers can safely integrate Kafka message production into existing Spring‑Boot services without risking data inconsistency or performance degradation.
Architect's Guide
Dedicated to sharing programmer-architect skills—Java backend, system, microservice, and distributed architectures—to help you become a senior architect.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.