Building Production-Ready Distributed Transactions with Apache Camel Saga Pattern in Spring Boot
Why Distributed Transactions Are Hard
Building Production-Ready Distributed Transactions with Apache Camel Saga Pattern in Spring Boot

AI image
Why Distributed Transactions Are Hard
Imagine you are building an e-commerce platform.
A customer clicks Place Order.
Behind the scenes, your system performs several operations:
- Create Order
- Reserve Inventory
- Charge Payment
- Create Shipment
- Send Notification
In a monolithic application, a single database transaction can handle this.
@Transactional
public void placeOrder() {
createOrder();
reserveInventory();
processPayment();
createShipment();
}
If any step fails, the entire transaction rolls back.
Unfortunately, this approach does not work in microservices.
Each service has:
- Its own database
- Its own deployment lifecycle
- Independent scaling
- Separate transactions
You cannot simply use:
@Transactional
across multiple services.
The Problem
Suppose the workflow is:
Order Created
↓
Inventory Reserved
↓
Payment Success
↓
Shipment Failed ❌
Now you have:
ServiceStateOrder ServiceOrder CreatedInventory ServiceInventory ReservedPayment ServiceMoney DeductedShipping ServiceFailed
The system is inconsistent.
The customer has been charged but the order was never shipped.
This is exactly why the Saga Pattern exists.
What is the Saga Pattern?
A Saga is a sequence of local transactions.
Each step has:
- Forward Action
- Compensation Action
Instead of:
Commit
Rollback
we have:
Action
Compensation
Example:
ActionCompensationCreate OrderCancel OrderReserve InventoryRelease InventoryCharge PaymentRefund PaymentCreate ShipmentCancel Shipment
Orchestration vs Choreography
Choreography
Order → Event
Inventory → Event
Payment → Event
Shipping → Event
Pros:
- Loosely coupled
Cons:
- Difficult debugging
- Event storms
- Hard to visualize flow
Orchestration
Order Service
↓
Saga Orchestrator
↓
Inventory
↓
Payment
↓
Shipping
Pros:
- Centralized control
- Easier debugging
- Better observability
- Easier compensation management
Apache Camel is an excellent orchestration engine.
Production Architecture
+----------------+
| API Gateway |
+--------+-------+
|
v
+--------------------+
| Order Service |
+----------+---------+
|
v
+----------------------+
| Camel Saga Engine |
+----------+-----------+
|
------------------------------------------------
| | |
v v v
+-------------+ +-------------+ +-------------+
| Inventory | | Payment | | Shipping |
| Service | | Service | | Service |
+------+------+ +------+------+ +------+------+
| | |
v v v
Inventory DB Payment DB Shipping DB
Project Structure
order-service
│
├── controller
│ └── OrderController
│
├── dto
│ └── OrderRequest
│
├── entity
│ └── OrderEntity
│
├── repository
│ └── OrderRepository
│
├── saga
│ ├── OrderSagaRoute
│ ├── SagaConstants
│ └── CompensationRoutes
│
├── service
│ ├── OrderService
│ ├── InventoryService
│ ├── PaymentService
│ └── ShippingService
│
├── exception
│ └── BusinessException
│
└── config
└── CamelConfig
Maven Dependencies
<dependency>
<groupId>org.apache.camel.springboot</groupId>
<artifactId>camel-saga-starter</artifactId>
<version>4.11.0</version>
</dependency>
<dependency>
<groupId>org.apache.camel.springboot</groupId>
<artifactId>camel-spring-boot-starter</artifactId>
<version>4.11.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
Order Request
public record OrderRequest(
String orderId,
String productId,
Integer quantity,
BigDecimal amount,
String customerId
) {
}
Order Entity
@Entity
@Table(name = "orders")
@Getter
@Setter
@NoArgsConstructor
public class OrderEntity {
@Id
private String orderId;
private String customerId;
private String productId;
private Integer quantity;
private BigDecimal amount;
@Enumerated(EnumType.STRING)
private OrderStatus status;
}
Order Status
public enum OrderStatus {
CREATED,
INVENTORY_RESERVED,
PAYMENT_SUCCESS,
SHIPPED,
COMPLETED,
FAILED
}
Saga Constants
public final class SagaConstants {
public static final String INVENTORY =
"direct:inventory";
public static final String PAYMENT =
"direct:payment";
public static final String SHIPPING =
"direct:shipping";
public static final String RELEASE_INVENTORY =
"direct:releaseInventory";
public static final String REFUND_PAYMENT =
"direct:refundPayment";
public static final String CANCEL_SHIPMENT =
"direct:cancelShipment";
}
Inventory Service
Reserve
@Service
@RequiredArgsConstructor
public class InventoryService {
public void reserve(OrderRequest order) {
log.info(
"Reserving inventory for {}",
order.orderId());
// call inventory microservice
inventoryClient.reserve(
order.productId(),
order.quantity());
}
public void release(OrderRequest order) {
log.info(
"Releasing inventory {}",
order.orderId());
inventoryClient.release(
order.productId(),
order.quantity());
}
}
Payment Service
@Service
@RequiredArgsConstructor
public class PaymentService {
public void charge(OrderRequest order) {
paymentGateway.charge(
order.customerId(),
order.amount());
}
public void refund(OrderRequest order) {
paymentGateway.refund(
order.customerId(),
order.amount());
}
}
Shipping Service
@Service
public class ShippingService {
public void createShipment(
OrderRequest order) {
if (RandomUtils.nextBoolean()) {
throw new RuntimeException(
"Shipping unavailable");
}
shippingClient.createShipment(
order.orderId());
}
public void cancelShipment(
OrderRequest order) {
shippingClient.cancel(
order.orderId());
}
}
Production Route
@Component
@RequiredArgsConstructor
public class OrderSagaRoute
extends RouteBuilder {
@Override
public void configure() {
onException(Exception.class)
.handled(false)
.log("Saga failed : ${exception.message}");
from("direct:createOrder")
.routeId("order-saga")
.saga()
.propagation(
SagaPropagation.REQUIRED)
.to("direct:saveOrder")
.to("direct:inventory")
.to("direct:payment")
.to("direct:shipping")
.to("direct:completeOrder")
.log("Order Saga Completed");
}
}
Persist Order
from("direct:saveOrder")
.process(exchange -> {
OrderRequest request =
exchange.getMessage()
.getBody(OrderRequest.class);
OrderEntity order =
mapper.toEntity(request);
order.setStatus(CREATED);
repository.save(order);
});
Inventory Step
from("direct:inventory")
.saga()
.compensation(
"direct:releaseInventory")
.bean(
InventoryService.class,
"reserve");
Compensation:
from("direct:releaseInventory")
.bean(
InventoryService.class,
"release");
Payment Step
from("direct:payment")
.saga()
.compensation(
"direct:refundPayment")
.bean(
PaymentService.class,
"charge");
Compensation:
from("direct:refundPayment")
.bean(
PaymentService.class,
"refund");
Shipping Step
from("direct:shipping")
.saga()
.compensation(
"direct:cancelShipment")
.bean(
ShippingService.class,
"createShipment");
Compensation:
from("direct:cancelShipment")
.bean(
ShippingService.class,
"cancelShipment");
Order Completion
from("direct:completeOrder")
.process(exchange -> {
OrderRequest order =
exchange.getMessage()
.getBody(OrderRequest.class);
repository.updateStatus(
order.orderId(),
COMPLETED);
});
Successful Flow
Create Order
↓
Reserve Inventory
↓
Charge Payment
↓
Create Shipment
↓
Mark Order Complete
↓
Send Email
Failure Scenario
Shipping Service throws:
throw new RuntimeException(
"Shipping unavailable");
Camel automatically triggers:
Create Order
↓
Reserve Inventory
↓
Charge Payment
↓
Shipping Failed
↓
Refund Payment
↓
Release Inventory
↓
Mark Order Failed
No manual rollback code.
No distributed transaction manager.
No XA transaction.
No 2PC.
Production Enhancements
1. Idempotency
@IdempotentConsumer(
header("orderId"),
memoryIdempotentRepository(10000))
Prevents duplicate saga execution.
2. Retry
errorHandler(
defaultErrorHandler()
.maximumRedeliveries(3)
.redeliveryDelay(2000));
3. Circuit Breaker
.circuitBreaker()
.resilience4jConfiguration()
.failureRateThreshold(50)
.slidingWindowSize(10)
.end()
4. Dead Letter Queue
errorHandler(
deadLetterChannel(
"kafka:order-failure"));
5. Distributed Tracing
camel:
tracing: true
Integrate with:
- Zipkin
- OpenTelemetry
- Jaeger
- Prometheus
- Grafana
Why Apache Camel Saga Works Well in Production

Final Thoughts
The biggest misconception about microservices is believing that distributed transactions require XA or two-phase commit.
Modern cloud-native systems rarely use 2PC because it:
- Blocks resources
- Reduces scalability
- Increases coupling
- Creates coordinator bottlenecks
Instead, production systems embrace eventual consistency using the Saga Pattern.
Apache Camel’s Saga EIP gives you a clean orchestration engine where every forward action has a corresponding compensation action, allowing you to build resilient distributed workflows without sacrificing scalability.
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.
메타데이터
- post_id
- fb75beff18eb
- slug
- building-production-ready-distributed-transactions-with-apache-camel-saga-pattern-in-spring-boot-fb75beff18eb
- url
- https://medium.com/javarevisited/building-production-ready-distributed-transactions-with-apache-camel-saga-pattern-in-spring-boot-fb75beff18eb
- canonical_url
- https://medium.com/javarevisited/building-production-ready-distributed-transactions-with-apache-camel-saga-pattern-in-spring-boot-fb75beff18eb
- author_url
- https://medium.com/@umeshcapg
- status
- ok
- fetched_at
- 2026-06-23 06:34:20