PrepZone Logo
PrepZone

In-Process Events

ApplicationEventPublisher, @EventListener and transactional event listeners.

Read these first

Why this matters

  • Tight coupling between OrderService and email, analytics, and loyalty-point logic makes every change ripple through the codebase.
  • Events decouple the publisher from listeners — add a new reaction to OrderPlaced without touching the checkout flow.
  • Transactional event listeners solve the classic problem of sending notifications before the database commit actually succeeds.
OrderServiceVaultCommerce
vaultcommerce.orders.placed.v1
InventoryConsumer
KafkaTemplate publishes to a topic. @KafkaListener consumes from a consumer group.

Event API building blocks

  • ApplicationEventPublisher — inject this to publish events from any service.
  • Plain POJO events — any class can be an event; records work well for immutable payloads.
  • @EventListener — marks a method that reacts to a specific event type.
  • @TransactionalEventListener — defers execution until a transaction commits, rolls back, or completes.
  • @Async on listeners — runs the handler off the publishing thread; combine with transactional listeners carefully.

Define and publish an event

Java
public record OrderPlacedEvent(
        Long orderId,
        String customerEmail,
        BigDecimal totalAmount,
        Instant placedAt
) {}
Java
@Service
public class OrderService {

    private final OrderRepository orderRepository;
    private final ApplicationEventPublisher events;

    public OrderService(OrderRepository orderRepository,
                        ApplicationEventPublisher events) {
        this.orderRepository = orderRepository;
        this.events = events;
    }

    @Transactional
    public OrderResponse placeOrder(PlaceOrderRequest request) {
        Order order = orderRepository.save(buildOrder(request));
        events.publishEvent(new OrderPlacedEvent(
                order.getId(),
                order.getCustomerEmail(),
                order.getTotal(),
                order.getPlacedAt()
        ));
        return OrderResponse.from(order);
    }
}

Listeners in separate beans

Java
@Component
public class OrderEventListeners {

    private final LoyaltyPointService loyaltyService;
    private final AnalyticsService analytics;

    public OrderEventListeners(LoyaltyPointService loyaltyService,
                               AnalyticsService analytics) {
        this.loyaltyService = loyaltyService;
        this.analytics = analytics;
    }

    @EventListener
    public void awardLoyaltyPoints(OrderPlacedEvent event) {
        loyaltyService.credit(event.customerEmail(), event.totalAmount());
    }

    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void trackOrderAnalytics(OrderPlacedEvent event) {
        analytics.recordPurchase(event.orderId(), event.totalAmount());
    }

    @TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
    public void logFailedCheckout(OrderPlacedEvent event) {
        log.warn("Order {} rolled back after event was published", event.orderId());
    }
}

Ordering and conditional listeners

Java
@EventListener
@Order(1)
public void reserveInventory(OrderPlacedEvent event) {
    inventoryService.reserve(event.orderId());
}

@EventListener(condition = "#event.totalAmount.compareTo(T(java.math.BigDecimal).valueOf(100)) > 0")
public void flagHighValueOrder(OrderPlacedEvent event) {
    fraudService.review(event.orderId());
}
Java
@Configuration
public class EventConfig {

    @Bean
    public ApplicationListener<ApplicationReadyEvent> warmCatalogCache() {
        return event -> catalogService.preloadBestsellers();
    }
}

Quick recall

Everything you need if you only revisit this box.

  • Inject ApplicationEventPublisher and call publishEvent with a plain object or record.
  • @EventListener methods live in any @Component; Spring wires them at startup.
  • Default listeners run synchronously on the publisher's thread — keep them fast or mark them @Async.
  • @TransactionalEventListener(AFTER_COMMIT) is the safe choice for emails, webhooks, and external calls.
  • Events decouple modules inside one app; they are not a distributed messaging solution.

Test yourself

Answer these before moving on — recall is what makes it stick.