with one click
spring-boot-saga-pattern
Use when implementing saga-based distributed consistency in Spring Boot 4, coordinating compensating transactions, or designing choreography and orchestration flows across services.
Menu
Use when implementing saga-based distributed consistency in Spring Boot 4, coordinating compensating transactions, or designing choreography and orchestration flows across services.
Use when configuring Spring Boot Actuator for production-grade monitoring, health probes, secured management endpoints, readiness/liveness checks, and Micrometer metrics in Spring Boot 4 applications.
Use when building Model Context Protocol servers in Spring Boot with Spring AI, exposing tools or resources, configuring transports, or integrating AI tool-calling workflows.
Use when adding caching to Spring Boot 4 services, configuring cache providers and TTL policies, invalidating stale data, or diagnosing cache hit and miss behavior.
Use when integrating Neo4j into a Spring Boot 4 backend with graph models, Cypher queries, reactive repositories, relationship mapping, or graph-focused testing patterns.
Use when implementing event-driven or message-based integration in Spring Boot 4, publishing domain events, coordinating Kafka or broker messaging, or applying outbox and idempotency patterns for reliable delivery.
Use when documenting reactive Spring Boot 4 HTTP APIs with SpringDoc OpenAPI, configuring Swagger UI, annotating endpoints, documenting security schemes, or defining request and response schemas.
| name | spring-boot-saga-pattern |
| description | Use when implementing saga-based distributed consistency in Spring Boot 4, coordinating compensating transactions, or designing choreography and orchestration flows across services. |
| allowed-tools | Read, Write, Edit, Bash, Glob, Grep |
Implements distributed transactions across microservices using the Saga Pattern. Replaces two-phase commit with a sequence of local transactions and compensating actions. Supports choreography ( event-driven) and orchestration (centralized coordinator) approaches with Kafka, RabbitMQ, or Axon Framework.
Trigger phrases: distributed transactions, saga pattern, compensating transactions, microservices transaction, eventual consistency, rollback across services, orchestration pattern, choreography pattern
Map the sequence of operations and their compensating transactions:
Order → Payment → Inventory → Shipment
↓ ↓ ↓ ↓
Cancel Refund Release Cancel
Validation: Verify every forward step has a corresponding compensation.
| Approach | Use Case | Stack |
|---|---|---|
| Choreography | Greenfield, few participants | Spring Cloud Stream + Kafka/RabbitMQ |
| Orchestration | Complex workflows, brownfield | Axon Framework, Eventuate Tram, Camunda |
Validation: Review team expertise and system complexity before choosing.
Each service completes its local ACID transaction atomically:
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderRepository orderRepository;
private final KafkaTemplate<String, Object> kafka;
@Transactional
public Order createOrder(CreateOrderCommand cmd) {
Order order = orderRepository.save(new Order(cmd.orderId(), cmd.items()));
kafka.send("order.created", new OrderCreatedEvent(order.getId(), order.getItems()));
return order;
}
}
Validation: Test that local transaction commits before event is published.
Every forward operation requires an idempotent compensation:
@Service
@RequiredArgsConstructor
public class PaymentService {
private final PaymentRepository paymentRepository;
private final KafkaTemplate<String, Object> kafka;
public void processPayment(PaymentRequest request) {
Payment payment = paymentRepository.save(new Payment(request.orderId(), request.amount()));
kafka.send("payment.processed", new PaymentProcessedEvent(payment.getId(), request.orderId()));
}
@Transactional
public void refundPayment(String paymentId) {
paymentRepository.findById(paymentId)
.ifPresent(p -> {
p.setStatus(REFUNDED);
paymentRepository.save(p);
kafka.send("payment.refunded", new PaymentRefundedEvent(paymentId));
});
}
}
Validation: Confirm compensation can execute safely multiple times (idempotency).
Configure Kafka with idempotent consumers:
@Configuration
@EnableKafka
public class KafkaConfig {
@Bean
public ConcurrentKafkaListenerContainerFactory<String, Object> kafkaListenerContainerFactory(
ConsumerFactory<String, Object> consumerFactory) {
ConcurrentKafkaListenerContainerFactory<String, Object> factory =
new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(consumerFactory);
factory.setCommonErrorHandler(new DefaultErrorHandler());
return factory;
}
}
Validation: Enable transactional ID and verify exactly-once semantics.
@Service
@RequiredArgsConstructor
public class OrderSagaOrchestrator {
private final KafkaTemplate<String, Object> kafka;
private final SagaStateRepository sagaStateRepo;
public void startSaga(OrderRequest request) {
String sagaId = UUID.randomUUID().toString();
sagaStateRepo.save(new SagaState(sagaId, STARTED, LocalDateTime.now()));
kafka.send("saga.order.start", new StartOrderSagaCommand(sagaId, request));
}
@KafkaListener(topics = "payment.failed")
public void handlePaymentFailed(PaymentFailedEvent event) {
kafka.send("order.compensate", new CompensateOrderCommand(event.getSagaId()));
kafka.send("inventory.compensate", new ReleaseInventoryCommand(event.getSagaId()));
sagaStateRepo.updateStatus(event.getSagaId(), FAILED);
}
}
Validation: Verify saga state persists before sending commands. Check compensation triggers on each failure path.
@Service
public class OrderEventHandler {
private final OrderService orderService;
private final KafkaTemplate<String, Object> kafka;
@KafkaListener(topics = "payment.processed", groupId = "order-service")
public void onPaymentProcessed(PaymentProcessedEvent event) {
try {
InventoryReservedEvent result = orderService.reserveInventory(event.toInventoryRequest());
kafka.send("inventory.reserved", result);
} catch (InsufficientInventoryException e) {
kafka.send("inventory.insufficient", new InsufficientInventoryEvent(event.getOrderId(), event.getPaymentId()));
}
}
}
Validation: Test that each event handler correctly triggers the next step or compensation.
@Configuration
public class SagaMetricsConfig {
@Bean
public MeterRegistry meterRegistry() {
return new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
}
}
Track: saga execution duration, compensation count, failure rate, stuck sagas.
Validation: Set up alerts for sagas exceeding expected duration.
Design:
Error Handling:
Monitoring:
// Application.java
@SpringBootApplication
@EnableKafka
@EnableKafkaListeners
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
// Event Classes (immutable)
public record OrderCreatedEvent(String orderId, List<OrderItem> items) {}
public record PaymentProcessedEvent(String paymentId, String orderId) {}
public record InventoryReservedEvent(String reservationId, String orderId) {}
public record PaymentFailedEvent(String orderId, String reason) {}
public record InsufficientInventoryEvent(String orderId, String paymentId) {}
// OrderService with compensation
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderRepository orderRepository;
private final KafkaTemplate<String, Object> kafka;
@KafkaListener(topics = "payment.failed", groupId = "order-service")
public void handleCompensation(PaymentFailedEvent event) {
orderRepository.findByOrderId(event.orderId())
.ifPresent(order -> {
order.setStatus(CANCELLED);
orderRepository.save(order);
});
}
}
// Command
@Aggregate
public class OrderAggregate {
@AggregateIdentifier
private String orderId;
@CommandHandler
public OrderAggregate(CreateOrderCommand cmd) {
apply(new OrderCreatedEvent(cmd.orderId(), cmd.items()));
}
@EventSourcingHandler
public void on(OrderCreatedEvent event) {
this.orderId = event.orderId();
}
@CommandHandler
public void handle(CancelOrderCommand cmd) {
apply(new OrderCancelledEvent(cmd.orderId(), cmd.reason()));
}
}