Transactional Pub/Sub in Spring for Multi-Instance, Production-Grade Systems
When building distributed systems with Spring Boot, one recurring challenge appears sooner or later:
Transactional Pub/Sub in Spring for Multi-Instance, Production-Grade Systems
When building distributed systems with Spring Boot, one recurring challenge appears sooner or later:
How do we publish and consume events in a way that is consistent with database transactions, especially when the system runs on multiple instances?
In simple setups, sending a message after saving something in the database seems fine. But in real-world, multi-instance microservice environments, this can easily lead to inconsistent data, lost messages, or duplicate events.
This article breaks down:
- What “transactional pub/sub” really means
- The safest patterns in multi-instance systems
- Which solutions work at scale
- What you should use in production with Spring
The Core Problem
Consider this common code:
@Transactional
public void createOrder(Order order) {
orderRepository.save(order);
publisher.publish(new OrderCreatedEvent(order));
}
Here is the risk:
- The message is published ✅
- The database transaction fails and rolls back ❌
- Now other services think the order exists, but it doesn’t
This breaks the fundamental principle of data consistency.
We need a way to ensure:
The message is only published if and when the database transaction is committed successfully.
That is where transactional pub/sub comes in.
Solution 1 — @TransactionalEventListener (Local only)
Spring provides a powerful but often misunderstood feature:
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
Example:
Publisher
@Transactional
public void createOrder(Order order) {
orderRepository.save(order);
applicationEventPublisher.publishEvent(new OrderCreatedEvent(order));
}
Listener
@Component
public class OrderListener {
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handle(OrderCreatedEvent event) {
System.out.println("Order successfully committed: "
+ event.getOrder().getId());
}
}
This guarantees:
✅ Event is fired only after commit ❌ No event if rollback occurs
However:
This mechanism is only local to that Spring instance.
If you run 10 instances of this app:
- Each instance receives only its own events
- There is no inter-instance communication
Use this when:
- You need in-process consistency
- For cache updates, audit logs, metrics, internal workflows
Do NOT use this for:
- Microservice-to-microservice messaging
- Multi-node coordination
- Integration events for other systems
Solution 2 — The Transactional Outbox Pattern (BEST)
The Transactional Outbox Pattern is the industry-proven solution for transactional messaging in distributed systems.
Instead of publishing directly to Kafka, SQS, or RabbitMQ, we write the event to an outbox table inside the same database transaction.
@Transactional
public void createOrder(Order order) {
orderRepository.save(order);
OutboxEvent event = new OutboxEvent(
"ORDER_CREATED",
convertToJson(order)
);
outboxRepository.save(event);
}
Now there is only one truth: the database.
After commit, a separate process publishes messages from the Outbox.
Publishing strategies:
Option A — Polling worker (recommended)
A background job runs:
SELECT *
FROM outbox
WHERE published = false
FOR UPDATE SKIP LOCKED
LIMIT 100;
Then it:
- Sends the event to Kafka/SQS/SNS
- Marks it as
published = true
This is 100% safe for multi-instance setups because:
SKIP LOCKEDprevents double processing- All services share one DB
- No race conditions
- No duplicates
✅ Kubernetes safe ✅ Multi-replica safe ✅ Cloud-native
Option B — CDC with Debezium (enterprise scale)
Instead of polling:
- Debezium listens to DB commit log
- Streams changes to Kafka
- Kafka distributes to consumers
This is what companies like Netflix, Uber, and Airbnb use.
Solution 3 — Kafka Transactions (Advanced & risky)
Kafka supports transactions:
spring.kafka.producer.transaction-id-prefix=tx-
@Transactional
public void createOrder(Order order) {
orderRepository.save(order);
kafkaTemplate.send("orders", order);
}
But in multi-instance environments:
- Each producer must have unique IDs
- Fencing issues can occur
- Failed instances can block partitions
- Hard to operate & debug
✅ Works ❌ Complex ❌ Not recommended for most systems
The Outbox Pattern is simply better.
Does This Work in Multi-Instance Setups?
Here is the honest answer:
Pattern Multi-instance safe? Recommended TransactionalEventListener ❌ (local only) For internal only Kafka Transactions ⚠️ (fragile) Rarely Outbox Pattern ✅✅✅ BEST Debezium (CDC) ✅✅✅✅✅ For scale
If you run:
- Multiple pods in Kubernetes
- Docker Swarm
- AWS ECS / EKS
- Multiple Spring Boot nodes
You want the Outbox pattern.
Final Architecture (Production Ready)
[ Spring Boot — Multiple Instances ]
|
| ( single transaction )
V
[ Database + Outbox Table ]
|
| (Publisher / CDC / Poller)
V
[ Kafka / SNS / SQS ]
|
V
[ Downstream Services ]
This architecture guarantees:
✅ Strong consistency ✅ No data loss ✅ No double sending ✅ Multi-instance safety ✅ Fully scalable
Perfect for:
- Microservices
- Event-driven systems
- Payment systems
- Airline / booking / fintech systems
Conclusion
If you’re using Spring Boot and care about consistency in distributed systems:
Use the Transactional Outbox pattern.
Not only is it the safest solution — it’s the same pattern used by the biggest technology companies in the world.
If you are serious about production-grade architecture in Spring, this is a must-have in your toolbox.
If you’d like, next I can provide you with:
✅ Full Spring Boot code ✅ Outbox entity & schema ✅ Multi-instance safe scheduler ✅ Kafka & AWS SQS versions ✅ Architecture diagram image for Medium
Just reply with: “Give me the code version”
메타데이터
- post_id
- 7eaac2b6894b
- slug
- transactional-pub-sub-in-spring-for-multi-instance-production-grade-systems-7eaac2b6894b
- url
- https://medium.com/@mtilab/transactional-pub-sub-in-spring-for-multi-instance-production-grade-systems-7eaac2b6894b
- canonical_url
- https://medium.com/@mtilab/transactional-pub-sub-in-spring-for-multi-instance-production-grade-systems-7eaac2b6894b
- author_url
- https://medium.com/@mtilab
- status
- ok
- fetched_at
- 2026-06-24 04:09:36