Application Events
IntermediateSpring's event system decouples components — one service publishes an event without knowing who handles it. @TransactionalEventListener ensures events fire only after the transaction commits, preventing side effects from rolled-back data.
Overview
ApplicationEventPublisher.publishEvent() broadcasts an event object to all @EventListener methods in the same ApplicationContext. By default, events are synchronous — the publisher waits for all listeners to finish. @Async on a listener makes it run in a thread pool. @TransactionalEventListener(phase = AFTER_COMMIT) is critical for database-triggered events like "user registered" or "order placed" — it guarantees the listener only fires after the transaction commits, so the listener sees committed data.
Publishing and Listening to Events
Create a plain Java class (or record) as the event object. Inject ApplicationEventPublisher in the publisher. Annotate listener methods with @EventListener — they can be in any Spring bean.
// Event object — plain class or Java record
public record UserRegisteredEvent(String userId, String email, Instant registeredAt) {}
// Publisher — inject ApplicationEventPublisher
@Service
public class UserService {
private final UserRepository userRepository;
private final ApplicationEventPublisher eventPublisher;
public UserService(UserRepository userRepository,
ApplicationEventPublisher eventPublisher) {
this.userRepository = userRepository;
this.eventPublisher = eventPublisher;
}
@Transactional
public User register(RegisterRequest req) {
User user = userRepository.save(new User(req));
// Publish event — listeners fire after this method returns
eventPublisher.publishEvent(
new UserRegisteredEvent(user.getId(), user.getEmail(), Instant.now())
);
return user;
}
}
// Listeners — any @Component can listen
@Component
public class WelcomeEmailListener {
private final EmailService emailService;
@EventListener
public void onUserRegistered(UserRegisteredEvent event) {
emailService.sendWelcome(event.email());
}
}
@Component
public class GamificationListener {
@EventListener
public void onUserRegistered(UserRegisteredEvent event) {
// Award signup XP
gamificationService.awardXp(event.userId(), "signup", 10);
}
}@TransactionalEventListener and @Async Events
@TransactionalEventListener(phase = AFTER_COMMIT) ensures the listener only fires after the database transaction commits — critical to avoid side effects on rollback. @Async decouples the listener from the publisher's thread.
@Component
public class OrderEventListener {
private final NotificationService notificationService;
private final InventoryService inventoryService;
// ✅ AFTER_COMMIT — listener sees committed data, won't fire on rollback
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
@Async // run in thread pool — doesn't block the order creation request
public void onOrderPlaced(OrderPlacedEvent event) {
// Safe: order is committed to DB before this runs
notificationService.sendOrderConfirmation(event.orderId(), event.userEmail());
inventoryService.reserveItems(event.items()); // external service call
}
// AFTER_ROLLBACK — audit failed transactions
@TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
public void onOrderFailed(OrderPlacedEvent event) {
auditService.logFailedOrder(event.orderId());
}
}
// Enable async support
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean
public Executor eventListenerExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(4);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("events-");
executor.initialize();
return executor;
}
}Key Points to Remember
- 1Events decouple publishers from listeners — the UserService doesn't know or care about welcome emails.
- 2@EventListener methods can be in any Spring bean — listeners discover events by parameter type.
- 3@TransactionalEventListener(AFTER_COMMIT) is essential for database-triggered events — prevents firing on rollback.
- 4Default event dispatch is synchronous — add @Async to run listeners in a thread pool.
- 5@EnableAsync on a @Configuration class is required for @Async to work.
- 6Use events for cross-cutting concerns (email, audit, cache invalidation) — not for primary business logic.
Interview Questions
Sign in to ask AriaWhat is the difference between @EventListener and @TransactionalEventListener?
Why would you use AFTER_COMMIT instead of the default event phase?
How do Spring application events differ from Kafka messages?
How does making an @EventListener @Async affect transaction propagation?
What happens if an @Async event listener throws an exception?
Ask Aria about Application Events
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.