spring-boot-event-driven-patterns — an installable skill for AI agents, published by giuseppe-trisciuoglio/developer-kit.
Spring Boot Event-Driven Patterns
Overview
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.
When to Use
Implementing event-driven microservices with Kafka messaging
Publishing domain events from aggregate roots in DDD architectures
Setting up transactional event listeners that fire after database commits
Adding async messaging with producers and consumers via Spring Kafka
Ensuring reliable event delivery using the transactional outbox pattern
Replacing synchronous calls with event-based communication between services
Quick Reference
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
Examples
Monolithic to Event-Driven Refactoring
Before (Anti-Pattern):
@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):
@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.
Instructions
1. Design Domain Events
Create immutable event classes extending a base DomainEvent class:
public 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.
2. Publish Events from Aggregates
Add domain events to aggregate roots, publish via ApplicationEventPublisher:
@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.
3. Handle Events Transactionally
Use @TransactionalEventListener for reliable event handling:
@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.
4. Configure Kafka Infrastructure
Configure KafkaTemplate for publishing, @KafkaListener for consuming:
spring:
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.
5. Implement Outbox Pattern
Create OutboxEvent entity for atomic event storage:
@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.
6. Handle Failure Scenarios
Implement retry logic, dead-letter queues, idempotent handlers:
@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.
7. Add Observability
Enable Spring Cloud Sleuth for distributed tracing, monitor metrics.
Best Practices
Use past tense naming: ProductCreated (not CreateProduct)
Keep events immutable: All fields should be final
Include correlation IDs: For tracing events across services
Use AFTER_COMMIT phase: Ensures events are published after successful database transaction
Implement idempotent handlers: Handle duplicate events gracefully
Add retry mechanisms: For failed event processing with exponential backoff
Implement dead-letter queues: For events that fail processing after retries
Log all failures: Include sufficient context for debugging
Make handlers order-independent: Event ordering is not guaranteed in distributed systems
Batch event processing: When handling high volumes
Monitor event latencies: Set up alerts for slow processing
References
dependency-setup.md — Maven/Gradle dependencies
configuration.md — Kafka and Spring Cloud Stream configuration
domain-events-design.md — Domain event design patterns
aggregate-root-patterns.md — Aggregate root with event publishing
event-publishing.md — Local and distributed event publishing
event-handling.md — Event handling and consumption patterns
outbox-pattern.md — Transactional outbox pattern for reliability
testing-strategies.md — Unit and integration testing approaches
examples.md — Complete working examples
event-driven-patterns-reference.md — Detailed reference documentation
Constraints and Warnings
Events published with @TransactionalEventListener only fire after transaction commit
Avoid publishing large objects in events (memory pressure, serialization issues)
Be cautious with async event handlers (separate threads, concurrency issues)
Kafka consumers must handle duplicate messages (implement idempotent processing)
Event ordering is not guaranteed in distributed systems (design handlers to be order-independent)
Never perform blocking operations in event listeners on the main transaction thread
Monitor for event processing backlogs (indicate system capacity issues)
Related Skills
spring-boot-security-jwt — JWT authentication for secure event publishing
spring-boot-test-patterns — Testing event-driven applications
aws-sdk-java-v2-lambda — Event-driven processing with AWS Lambda
langchain4j-tool-function-calling-patterns — AI-driven event processingdon't have the plugin yet? install it then click "run inline in claude" again.