spring-boot-event-driven-patterns

Event-Driven Architecture patterns for Spring Boot using domain events, transactional listeners, and Kafka messaging. Covers domain event design, ApplicationEventPublisher integration, and @TransactionalEventListener configuration for local event handling with AFTER_COMMIT phase guarantees Supports distributed messaging via Kafka and Spring Cloud Stream for inter-service communication with functional consumer beans Implements the transactional outbox pattern for reliable event publishing and includes retry logic with dead-letter queue handling Provides testing strategies using Testcontainers for Kafka integration and includes best practices for event naming, idempotent handlers, and correlation ID tracing

INSTALLATION
npx skills add https://github.com/giuseppe-trisciuoglio/developer-kit --skill spring-boot-event-driven-patterns
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

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

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 processing
BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card