Using Spring Transaction Hooks to Send Kafka Messages After Transaction Commit
This article explains how to leverage Spring's TransactionSynchronizationManager to detect active transactions and register synchronization callbacks so that Kafka messages are sent asynchronously only after a transaction successfully commits, ensuring data consistency without impacting the main business flow.
Today the author introduces a practical case of using Spring transaction hook functions to ensure that Kafka messages are sent only after a transaction commits, avoiding impact on the main business logic.
Case Background
In a payment system each account's fund flow must be archived; the requirement is to push a message to Kafka when writing the flow, and a separate archive service consumes the message and writes to a database with write permission only for the archive system.
Solution Design
The team decides to create a second‑party library (a Spring Boot starter) that provides a simple API for sending messages to Kafka. The library must:
Be a Spring Boot starter.
Use the raw Kafka producer API instead of Spring’s KafkaTemplate to avoid conflicts.
Offer an easy‑to‑use API.
Support transaction semantics so that message sending occurs after the surrounding transaction commits.
Implementation – TransactionSynchronizationManager
The core of the solution relies on TransactionSynchronizationManager . The following Java code shows a simplified implementation:
private final ExecutorService executor = Executors.newSingleThreadExecutor();
public void sendLog() {
// Check if a transaction is active
if (!TransactionSynchronizationManager.isSynchronizationActive()) {
// No transaction – send asynchronously
executor.submit(() -> {
try {
// send message 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 message to Kafka
} catch (Exception e) {
// log error
}
});
}
}
});
}The method TransactionSynchronizationManager.isSynchronizationActive() checks a thread‑local variable that the transaction manager sets when a transaction begins. By registering a TransactionSynchronizationAdapter , the library receives a callback after the transaction finishes; if the status is STATUS_COMMITTED , the message is sent asynchronously.
Key Points
1. Use asynchronous sending to minimise impact on the main business. 2. Ensure the hook runs after transaction commit to keep data consistency. 3. Keep the logic inside the library and avoid thread switches, otherwise the thread‑local flag will be lost.
Conclusion
Using Spring’s transaction synchronization facilities allows a clean way to attach Kafka‑sending logic to any transactional method without altering business code, providing reliable, non‑blocking message delivery after successful commits.
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.
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.