Home/Learn/Spring Boot/Application Events & Listeners

Application Events & Listeners

Intermediate
Configuration

Spring fires lifecycle events (ApplicationStartedEvent, ApplicationReadyEvent, etc.) that you can handle with @EventListener to run custom startup logic.

Overview

Spring's event system provides a lightweight publish-subscribe mechanism within a single application context. The framework publishes well-known lifecycle events (ContextRefreshedEvent, ApplicationStartedEvent, ApplicationReadyEvent, ApplicationFailedEvent) that you can observe to run initialization logic, register resources, or perform health checks. You can also define custom domain events (by extending ApplicationEvent or using any POJO) and publish them with ApplicationEventPublisher. @EventListener handles events synchronously in the same thread; @Async @EventListener handles them asynchronously in a separate thread pool. This pattern decouples event producers from consumers without adding a message broker.

Built-in lifecycle events and @EventListener

Spring Boot fires events in a specific order during startup: ApplicationStartingEvent → ApplicationEnvironmentPreparedEvent → ApplicationContextInitializedEvent → ApplicationPreparedEvent → ContextRefreshedEvent → ApplicationStartedEvent → ApplicationReadyEvent. Use ApplicationReadyEvent for code that should run after the full context is ready (including embedded server). Use CommandLineRunner/ApplicationRunner for simple startup tasks.

Java — built-in lifecycle event listeners
@Component
public class StartupInitializer {

    private final DataCache cache;

    // Runs after context is fully ready (including embedded Tomcat)
    @EventListener(ApplicationReadyEvent.class)
    public void onApplicationReady(ApplicationReadyEvent event) {
        log.info("Application started in {}ms — warming up cache",
            event.getTimeTaken().toMillis());
        cache.warmUp();
    }

    // Runs on context refresh (also on each refresh in dev with DevTools)
    @EventListener(ContextRefreshedEvent.class)
    public void onContextRefreshed() {
        log.debug("Context refreshed");
    }

    // Handle failed startup
    @EventListener(ApplicationFailedEvent.class)
    public void onApplicationFailed(ApplicationFailedEvent event) {
        log.error("Startup failed", event.getException());
        alertingService.sendAlert("Startup failure: " + event.getException().getMessage());
    }
}

Custom domain events with ApplicationEventPublisher

Publish custom events anywhere in the application using ApplicationEventPublisher. Events can be plain POJOs (no need to extend ApplicationEvent since Spring 4.2). Listeners are resolved by the event class type. This pattern is excellent for decoupling: the OrderService fires an OrderPlacedEvent without knowing what listeners exist.

Java — custom domain events with ApplicationEventPublisher
// Custom event POJO (Spring 4.2+ — no extends ApplicationEvent needed)
public record OrderPlacedEvent(Long orderId, String customerId, BigDecimal amount) {}

// Publisher — inject ApplicationEventPublisher
@Service
public class OrderService {

    private final ApplicationEventPublisher publisher;

    @Transactional
    public Order placeOrder(OrderRequest req) {
        Order order = orderRepository.save(new Order(req));
        // Publish after successful save — within same transaction
        publisher.publishEvent(new OrderPlacedEvent(order.getId(),
            req.customerId(), req.total()));
        return order;
    }
}

// Listener 1 — email notification
@Component
public class EmailNotificationListener {
    @EventListener
    public void handleOrderPlaced(OrderPlacedEvent event) {
        emailService.sendOrderConfirmation(event.customerId(), event.orderId());
    }
}

// Listener 2 — audit log (different class, same event type)
@Component
public class AuditListener {
    @EventListener
    public void auditOrderPlaced(OrderPlacedEvent event) {
        auditLog.record("ORDER_PLACED", event.orderId(), event.amount());
    }
}

@Async events and @TransactionalEventListener

@Async @EventListener processes events in a separate thread pool, preventing listener failures from affecting the publisher. @TransactionalEventListener binds the event to a transaction phase (AFTER_COMMIT, AFTER_ROLLBACK, BEFORE_COMMIT) — ensuring the listener fires only after the transaction successfully commits, which is critical for events that trigger external side effects like sending emails or pushing to a queue.

Java — @Async events and @TransactionalEventListener phases
// Async listener — processed in separate thread, won't block OrderService
@Component
public class AsyncInventoryListener {

    @Async
    @EventListener
    public void updateInventory(OrderPlacedEvent event) {
        // runs in taskExecutor thread pool — separate from OrderService transaction
        inventoryService.reserveStock(event.orderId());
    }
}

// @TransactionalEventListener — fires only AFTER transaction commits
// Critical: without this, email could be sent even if the transaction rolls back
@Component
public class TransactionalEmailListener {

    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void sendConfirmationAfterCommit(OrderPlacedEvent event) {
        // guaranteed: order IS persisted before this fires
        emailService.sendOrderConfirmation(event.customerId(), event.orderId());
    }

    @TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
    public void alertOnRollback(OrderPlacedEvent event) {
        log.error("Order transaction rolled back — order {} not saved", event.orderId());
    }
}

// Enable @Async — add to any @Configuration class
@Configuration
@EnableAsync
public class AsyncConfig {}

Key Points to Remember

  • 1Spring fires lifecycle events in order: Starting → EnvironmentPrepared → ContextInitialized → Prepared → ContextRefreshed → Started → Ready
  • 2Use ApplicationReadyEvent for post-startup init (cache warm-up) — it fires after embedded server is ready
  • 3Custom events can be plain POJOs since Spring 4.2; use records for immutable, self-documenting events
  • 4@EventListener is synchronous (same thread); @Async @EventListener runs in a thread pool (requires @EnableAsync)
  • 5@TransactionalEventListener(phase=AFTER_COMMIT) fires only after the transaction commits — prevents side effects on rollback
  • 6Multiple @EventListener methods can handle the same event type independently — decoupled consumers

Interview Questions

Sign in to ask Aria
1

What is the difference between ApplicationStartedEvent and ApplicationReadyEvent?

EasyThoughtworks
2

Why should you use @TransactionalEventListener instead of @EventListener when sending emails after saving an order?

MediumAmazon
3

How does @Async @EventListener differ from a regular @EventListener in terms of thread and exception handling?

MediumGoogle
4

What happens if a @TransactionalEventListener fires AFTER_COMMIT but no transaction is active when the event is published?

HardNetflix
5

How would you use Spring events to implement an outbox pattern for reliable event publishing?

HardUber

Ask Aria about Application Events & Listeners

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…