
A Fantastic Journey of Distributed Transactions in Spring Boot
In modern microservice architectures, distributed transactions are one of the most challenging problems developers face. Spring Boot makes it easy to build independent services, but ensuring data consistency across multiple services and databases is far more difficult than writing REST APIs or deploying containers.
Imagine an e-commerce system where placing an order involves:
- Creating an order record
- Deducting inventory
- Processing payment
- Updating shipping status
In a monolithic application using a single database, a local transaction can guarantee consistency. But in a distributed architecture, each module often owns its own database.
Now the real challenge appears:
What happens if the payment succeeds but inventory deduction fails?
This is where distributed transactions become essential.
In this article, we will explore:
- What distributed transactions are
- Why they are difficult
- Popular distributed transaction patterns
- How to implement them in Spring Boot
- Real-world examples using Seata and RabbitMQ
- Best practices for production systems
1. What Exactly Is a Distributed Transaction?
A distributed transaction is a transaction that spans multiple services, databases, or systems.
The core goal is simple:
Either all operations succeed together, or all operations fail together.
Unlike local transactions, distributed transactions involve:
- Multiple JVMs
- Multiple databases
- Network communication
- Service failures
- Message delays
- Partial failures
This makes distributed consistency significantly harder.
2. Why Distributed Transactions Are Difficult
In distributed systems, failures are inevitable.
Possible failure scenarios include:

For example:
- Order service creates an order
- Inventory service deducts stock
- Payment service crashes
Now:
- Order exists
- Inventory reduced
- Payment missing
This creates data inconsistency.
Distributed transaction mechanisms solve this problem.
3. Common Distributed Transaction Solutions
There is no perfect solution.
Each approach balances:
- Consistency
- Availability
- Performance
- Complexity
The most common patterns are:
- Two-Phase Commit (2PC)
- TCC (Try-Confirm-Cancel)
- Saga Pattern
- Eventual Consistency using Message Queues
Let’s examine them one by one.
4. Two-Phase Commit (2PC)
How 2PC Works
2PC is the classic distributed transaction protocol.
It consists of two phases:
Phase 1 — Prepare
The coordinator asks all participants:
“Can you commit this transaction?”
Participants:
- Execute transaction logic
- Lock resources
- Do NOT commit yet
- Reply READY or FAIL
Phase 2 — Commit / Rollback
If all participants return READY:
- Coordinator sends COMMIT
Otherwise:
- Coordinator sends ROLLBACK
2PC Workflow Diagram
Coordinator
|
|---- PREPARE ----> Service A
|---- PREPARE ----> Service B
|
|<--- READY -------|
|<--- READY -------|
|
|---- COMMIT ----->|
|---- COMMIT ----->|
Advantages of 2PC
- Strong consistency
- Simple concept
- Suitable for banking systems
Disadvantages of 2PC
1. Blocking Problem
Participants hold locks while waiting for coordinator decisions.
This reduces system throughput.
2. Coordinator Single Point of Failure
If the coordinator crashes:
- Participants remain blocked
- Resources remain locked
3. Poor Performance
Network communication and locking increase latency.
Spring Boot Implementation with JTA
Common JTA transaction managers:
- Atomikos
- Narayana
- Bitronix
Example dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>
Example transaction:
@Transactional
public void transferMoney() {
orderRepository.save(order);
paymentRepository.save(payment);
}
5. TCC (Try-Confirm-Cancel)
TCC is one of the most popular distributed transaction solutions in modern microservices.
Instead of relying on database locking, it uses business-level compensation.
TCC Phases

Example: Inventory Reservation
Try
Freeze inventory:
Available Stock = 100
Freeze 2 units
Available = 98
Frozen = 2
Confirm
Payment succeeds:
Frozen stock becomes deducted stock
Cancel
Payment fails:
Release frozen inventory
Why TCC Is Powerful
TCC avoids long database locks.
It provides:
- Better scalability
- Better fault tolerance
- Better performance
TCC Challenges
Developers must manually implement:
- Try
- Confirm
- Cancel
Additionally:
- Confirm must be idempotent
- Cancel must be idempotent
This is critical.
Idempotency Example
A duplicate Confirm request should NOT deduct stock twice.
Bad implementation:
stock = stock - quantity;
Correct approach:
if (!alreadyProcessed(transactionId)) {
stock = stock - quantity;
}6. Implementing TCC with Seata in Spring Boot
Step 1 — Add Dependencies
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.7.0</version>
</dependency>
Step 2 — Configure Seata
application.yml
seata:
enabled: true
application-id: order-service
tx-service-group: ecommerce-tx-group
service:
vgroup-mapping:
ecommerce-tx-group: default
grouplist:
default: 127.0.0.1:8091
client:
rm:
async-commit-buffer-limit: 10000
tm:
commit-retry-count: 5
rollback-retry-count: 5
Step 3 — Define TCC Interfaces
public interface StockService {
boolean tryReduceStock(Long productId,
Integer quantity);
boolean confirmReduceStock(Long productId,
Integer quantity);
boolean cancelReduceStock(Long productId,
Integer quantity);
}Step 4 — Implement Stock Logic
@Service
public class StockServiceImpl implements StockService {
@Override
public boolean tryReduceStock(Long productId,
Integer quantity) {
// Freeze inventory
return true;
}
@Override
public boolean confirmReduceStock(Long productId,
Integer quantity) {
// Deduct frozen inventory
return true;
}
@Override
public boolean cancelReduceStock(Long productId,
Integer quantity) {
// Release frozen inventory
return true;
}
}
Step 5 — Global Distributed Transaction
@Service
public class OrderService {
@Autowired
private StockService stockService;
@Autowired
private PaymentService paymentService;
@GlobalTransactional
public boolean placeOrder(Order order) {
boolean created = createOrder(order);
if (!created) {
return false;
}
boolean frozen =
stockService.tryReduceStock(
order.getProductId(),
order.getQuantity());
if (!frozen) {
return false;
}
boolean paymentSuccess =
paymentService.processPayment(
order.getPaymentInfo());
if (!paymentSuccess) {
stockService.cancelReduceStock(
order.getProductId(),
order.getQuantity());
return false;
}
return stockService.confirmReduceStock(
order.getProductId(),
order.getQuantity());
}
private boolean createOrder(Order order) {
return true;
}
}
TCC Transaction Flow
Order Service
|
|---- Try Freeze Stock ----> Stock Service
|
|---- Process Payment -----> Payment Service
|
|---- Confirm Stock -------> Stock Service
If payment fails:
Order Service
|
|---- Cancel Stock --------> Stock Service
7. Saga Pattern
The Saga pattern breaks a large distributed transaction into multiple local transactions.
Each local transaction has a compensation transaction.
Saga Example
Step 1
Create order
Step 2
Deduct inventory
Step 3
Process payment
Failure Scenario
If payment fails:
- Refund inventory
- Cancel order
Saga Compensation Flow
Create Order
|
Deduct Inventory
|
Payment Failed
|
Compensate Inventory
|
Cancel Order
Advantages of Saga
- High scalability
- No global locks
- Better throughput
Disadvantages
- Eventual consistency only
- Complex compensation logic
- Hard debugging
Saga Frameworks in Spring Boot
Popular frameworks:
- Axon Framework
- Eventuate Tram
Axon is especially useful for:
- CQRS
- Event sourcing
- Long-running workflows
8. Eventual Consistency with Message Queues
This is the most common approach in internet-scale systems.
Instead of synchronous distributed transactions:
- Services communicate asynchronously
- Message queues ensure eventual consistency
Why Big Companies Prefer MQ-Based Transactions
Companies like:
- Amazon
- G-PAY
- Uber
Prefer eventual consistency because:
- Higher scalability
- Better fault tolerance
- Lower latency
Spring Boot + RabbitMQ Example
Step 1 — Add Dependency
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
Step 2 — Configure RabbitMQ
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
Step 3 — Send Messages
@Service
public class OrderService {
@Autowired
private RabbitTemplate rabbitTemplate;
public boolean placeOrder(Order order) {
boolean created = createOrder(order);
if (!created) {
return false;
}
rabbitTemplate.convertAndSend(
"stock-exchange",
"stock-routing-key",
order);
rabbitTemplate.convertAndSend(
"payment-exchange",
"payment-routing-key",
order);
return true;
}
private boolean createOrder(Order order) {
return true;
}
}
Step 4 — Consume Messages
@Component
public class StockReceiver {
@RabbitListener(queues = "stock-queue")
public void handleStockMessage(Order order) {
boolean reduced =
reduceStock(order.getProductId(),
order.getQuantity());
if (!reduced) {
// Retry logic
// Dead-letter queue
// Compensation handling
}
}
private boolean reduceStock(Long productId,
Integer quantity) {
return true;
}
}
Essential Production Features
Message-based systems MUST support:
- Retry mechanisms
- Dead-letter queues
- Idempotency
- Message deduplication
- Monitoring
- Compensation jobs
Outbox Pattern (Highly Recommended)
One major issue:
Database commit succeeds
BUT
Message sending fails
This creates inconsistency.
The Outbox Pattern solves this.
Outbox Flow
- Save business data
- Save event into OUTBOX table
- Commit local transaction
- Background worker publishes messages
- Mark message as SENT
Outbox Table Example
CREATE TABLE outbox_event (
id BIGINT PRIMARY KEY,
event_type VARCHAR(100),
payload TEXT,
status VARCHAR(20),
created_at TIMESTAMP
);
9. Comparison of Distributed Transaction Solutions

10. Which Solution Should You Choose?
Use 2PC When
- Strong consistency is mandatory
- Transaction volume is low
- Banking systems
Use TCC When
- Business can reserve resources
- Strong consistency required
- High scalability needed
Use Saga When
- Long-running transactions
- Event-driven architecture
- Complex workflows
Use MQ Eventual Consistency When
- Internet-scale traffic
- High availability required
- Temporary inconsistency acceptable
11. Best Practices for Production Systems
1. Design for Idempotency
Always assume retries will happen.
2. Avoid Distributed Locks
Locks reduce throughput dramatically.
3. Prefer Eventual Consistency
Strong consistency is expensive.
Most internet systems use eventual consistency.
4. Add Monitoring
Track:
- Transaction failures
- Retry counts
- Compensation execution
- Message backlog
5. Use Dead-Letter Queues
Never lose failed messages.
6. Implement Compensation Carefully
Compensation logic is business-critical.
Test it thoroughly.
12. Final Thoughts
Distributed transactions are one of the hardest problems in distributed systems engineering.
There is no universal solution.
Every approach involves trade-offs between:
- Consistency
- Performance
- Availability
- Complexity
In Spring Boot ecosystems:
- Seata provides excellent TCC support
- RabbitMQ and Kafka enable scalable eventual consistency
- Saga frameworks support complex workflows
- JTA still works for legacy enterprise systems
For modern microservices, eventual consistency and event-driven architectures are increasingly becoming the industry standard.
Understanding distributed transactions deeply is a major milestone for every backend engineer.
Once mastered, you can build systems that remain reliable even under massive scale, network failures, and service outages.
That is the true power of distributed system design.
Thank you for reading!
If you found this article useful, feel free to give it a clap 👏, share it with your friends, and follow for more deep dives into distributed systems, Spring Boot architecture, Kafka, Redis, and high-scale backend engineering.
😊 Your support is the biggest motivation to continue sharing technical insights.
A Fantastic Journey of Distributed Transactions in Spring Boot was originally published in Javarevisited on Medium, where people are continuing the conversation by highlighting and responding to this story.
This post first appeared on Read More