Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Provides Event-Driven Architecture (EDA) patterns for Spring Boot — creates domain events, configures ApplicationEvent and @TransactionalEventListener, sets up Kafka producers and consumers, and implements the transactional outbox pattern for reliable distributed messaging. Use when implementing event-driven systems in Spring Boot, setting up async messaging with Kafka, publishing domain events from DDD aggregates, or needing reliable event publishing with the outbox pattern.
.claude/skills/giuseppe-trisciuoglio-spring-boot-event-driven-patterns/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-11 | ✗→✓ | ▲ Improved | 63% | 0% |
| case-13 | ✗→✓ | ▲ Improved | 67% | 0% |
| case-17 | ✗→✓ | ▲ Improved | 60% | 0% |
| case-18 | ✓→✗ | ▼ Worse | 49% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 50% | 0% |
Implement Event-Driven Architecture (EDA) patterns in Spring Boot 3.x using domain events, ApplicationEventPublisher, @TransactionalEventListener, and distributed messaging with Kafka and Spring Cloud Stream.
| Concept | Description | |---------|-------------| | Domain Events | Immutable events extending DomainEvent base class with eventId, occurredAt, correlationId | | Event Publishing | ApplicationEventPublisher.publishEvent() for local, KafkaTemplate for distributed | | Event Listening | @TransactionalEventListener(phase = AFTER_COMMIT) for reliable handling | | Kafka | @KafkaListener(topics = "...") for distributed event consumption | | Spring Cloud Stream | Functional programming model with Consumer beans | | Outbox Pattern | Atomic event storage with business data, scheduled publisher |
Before (Anti-Pattern):
java@Transactional public Order processOrder(OrderRequest request) { Order order = orderRepository.save(request); inventoryService.reserve(order.getItems()); // Blocking paymentService.charge(order.getPayment()); // Blocking emailService.sendConfirmation(order); // Blocking return order; }
After (Event-Driven):
java@Transactional public Order processOrder(OrderRequest request) { Order order = Order.create(request); orderRepository.save(order); // Publish event after transaction commits eventPublisher.publishEvent(new OrderCreatedEvent(order.getId(), order.getItems())); return order; } @Component public class OrderEventHandler { @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) public void handleOrderCreated(OrderCreatedEvent event) { // Execute asynchronously after the order is saved inventoryService.reserve(event.getItems()); paymentService.charge(event.getPayment()); } }
See examples.md for complete working examples.
Create immutable event classes extending a base DomainEvent class:
javapublic abstract class DomainEvent { private final UUID eventId; private final LocalDateTime occurredAt; private final UUID correlationId; } public class ProductCreatedEvent extends DomainEvent { private final ProductId productId; private final String name; private final BigDecimal price; }
See domain-events-design.md for patterns.
Add domain events to aggregate roots, publish via ApplicationEventPublisher:
java@Service @Transactional public class ProductService { public Product createProduct(CreateProductRequest request) { Product product = Product.create(request.getName(), request.getPrice(), request.getStock()); repository.save(product); product.getDomainEvents().forEach(eventPublisher::publishEvent); product.clearDomainEvents(); return product; } }
See aggregate-root-patterns.md for DDD patterns.
Use @TransactionalEventListener for reliable event handling:
java@Component public class ProductEventHandler { @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) public void onProductCreated(ProductCreatedEvent event) { notificationService.sendProductCreatedNotification(event.getName()); } }
Validate: Confirm the event handler fires only after the transaction commits by checking that the database state is committed before the handler executes.
See event-handling.md for handling patterns.
Configure KafkaTemplate for publishing, @KafkaListener for consuming:
yamlspring: kafka: bootstrap-servers: localhost:9092 producer: value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
Validate: Send a test event via KafkaTemplate and confirm it appears in the consumer logs before proceeding to production patterns.
See dependency-setup.md and configuration.md.
Create OutboxEvent entity for atomic event storage:
java@Entity public class OutboxEvent { private UUID id; private String aggregateId; private String eventType; private String payload; private LocalDateTime publishedAt; }
Validate: Confirm the scheduled processor picks up pending events by checking the publishedAt timestamp is set after the scheduled run.
Scheduled processor publishes pending events. See outbox-pattern.md.
Implement retry logic, dead-letter queues, idempotent handlers:
java@RetryableTopic(attempts = "3") @KafkaListener(topics = "product-events") public void handleProductEvent(ProductCreatedEventDto event) { orderService.onProductCreated(event); }
Validate: Confirm messages reach the dead-letter topic after exhausting retries before moving to observability.
Enable Spring Cloud Sleuth for distributed tracing, monitor metrics.
ProductCreated (not CreateProduct)@TransactionalEventListener only fire after transaction commitspring-boot-security-jwt — JWT authentication for secure event publishingspring-boot-test-patterns — Testing event-driven applicationsaws-sdk-java-v2-lambda — Event-driven processing with AWS Lambdalangchain4j-tool-function-calling-patterns — AI-driven event processing| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-02 | fail→fail | 13,883 | 17,558 | +26% | 1 | 1 | 0% | 2,545 | 5,482 | +115% | 0 | 0 | — |
case-01 | fail→fail | 15,568 | 14,327 | -8% | 1 | 1 | 0% | 2,847 | 4,926 | +73% | 0 | 0 | — |
case-03 | pass→pass | 19,222 | 38,955 | +103% | 1 | 1 | 0% | 3,698 | 5,546 | +50% | 0 | 0 | — |
case-04 | pass→pass | 16,710 | 12,125 | -27% | 1 | 1 | 0% | 3,373 | 4,444 | +32% | 0 | 0 | — |
case-05 | pass→pass | 9,875 | 8,230 | -17% | 1 | 1 | 0% | 1,851 | 3,474 | +88% | 0 | 0 | — |
case-06 | pass→pass | 17,102 | 12,974 | -24% | 1 | 1 | 0% | 3,590 | 4,753 | +32% | 0 | 0 | — |
case-07 | fail→fail | 15,807 | 9,070 | -43% | 1 | 1 | 0% | 2,835 | 3,450 | +22% | 0 | 0 | — |
case-08 | pass→pass | 4,932 | 3,426 | -31% | 1 | 1 | 0% | 840 | 2,529 | +201% | 0 | 0 | — |
case-09 | pass→pass | 15,518 | 11,590 | -25% | 1 | 1 | 0% | 2,843 | 4,091 | +44% | 0 | 0 | — |
case-10 | pass→pass | 3,527 | 4,092 | +16% | 1 | 1 | 0% | 647 | 2,461 | +280% | 0 | 0 | — |
case-11 | fail→pass | 14,828 | 11,971 | -19% | 1 | 1 | 0% | 2,587 | 4,219 | +63% | 0 | 0 | — |
case-12 | fail→fail | 16,299 | 13,827 | -15% | 1 | 1 | 0% | 2,789 | 4,347 | +56% | 0 | 0 | — |
case-13 | fail→pass | 27,409 | 14,573 | -47% | 1 | 1 | 0% | 2,896 | 4,836 | +67% | 0 | 0 | — |
case-14 | pass→pass | 10,726 | 9,298 | -13% | 1 | 1 | 0% | 2,020 | 3,576 | +77% | 0 | 0 | — |
case-15 | pass→pass | 19,035 | 13,469 | -29% | 1 | 1 | 0% | 3,008 | 4,606 | +53% | 0 | 0 | — |
case-16 | pass→pass | 27,994 | 29,394 | +5% | 1 | 1 | 0% | 2,670 | 4,452 | +67% | 0 | 0 | — |
case-17 | fail→pass | 19,749 | 5,695 | -71% | 1 | 1 | 0% | 1,772 | 2,829 | +60% | 0 | 0 | — |
case-18 | pass→fail | 14,602 | 9,086 | -38% | 1 | 1 | 0% | 2,310 | 3,437 | +49% | 0 | 0 | — |
case-19 | pass→pass | 12,956 | 7,764 | -40% | 1 | 1 | 0% | 2,016 | 3,143 | +56% | 0 | 0 | — |
case-20 | pass→pass | 12,963 | 7,482 | -42% | 1 | 1 | 0% | 2,191 | 3,302 | +51% | 0 | 0 | — |
case-21 | pass→pass | 14,527 | 11,098 | -24% | 1 | 1 | 0% | 2,491 | 3,837 | +54% | 0 | 0 | — |
case-22 | pass→pass | 7,344 | 4,198 | -43% | 1 | 1 | 0% | 1,232 | 2,628 | +113% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +9 percentage points is the difference between those two pass rates over the 22 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.