Home/Learn/Spring Boot/Application Events

Application Events

Intermediate
Production & Advanced

Spring'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.

Java — ApplicationEventPublisher + @EventListener
// 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.

Java — @TransactionalEventListener(AFTER_COMMIT) + @Async
@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 Aria
1

What is the difference between @EventListener and @TransactionalEventListener?

MediumAmazon
2

Why would you use AFTER_COMMIT instead of the default event phase?

MediumGoldman Sachs
3

How do Spring application events differ from Kafka messages?

MediumThoughtWorks
4

How does making an @EventListener @Async affect transaction propagation?

HardAtlassian
5

What happens if an @Async event listener throws an exception?

HardNetflix

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.

Loading discussion…