← Back to list

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

Why Distributed Transactions Are Hard

Umesh Kumar Yadav in Javarevisited · 2026-06-22 06:16 · 50 claps · 4.2 min read paywalled
#java #apache-camel #software-development #software-engineering #spring-boot
Open on Medium ↗

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

AI image

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:

  1. Create Order
  2. Reserve Inventory
  3. Charge Payment
  4. Create Shipment
  5. 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:

  1. Forward Action
  2. 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