Application Events & Listeners
IntermediateSpring 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.
@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.
// 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.
// 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 AriaWhat is the difference between ApplicationStartedEvent and ApplicationReadyEvent?
Why should you use @TransactionalEventListener instead of @EventListener when sending emails after saving an order?
How does @Async @EventListener differ from a regular @EventListener in terms of thread and exception handling?
What happens if a @TransactionalEventListener fires AFTER_COMMIT but no transaction is active when the event is published?
How would you use Spring events to implement an outbox pattern for reliable event publishing?
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.