Spring Boot Application Events

1 minute read

Events decouple your code — the publisher doesn’t know who’s listening. Add listeners without modifying existing code.


Custom Event

// Event class
public record BookCreatedEvent(Long bookId, String title, String author, Instant timestamp) {
    public BookCreatedEvent(Long bookId, String title, String author) {
        this(bookId, title, author, Instant.now());
    }
}

Publishing Events

@Service
public class BookService {

    private final BookRepository bookRepository;
    private final ApplicationEventPublisher eventPublisher;

    public BookService(BookRepository bookRepository, ApplicationEventPublisher eventPublisher) {
        this.bookRepository = bookRepository;
        this.eventPublisher = eventPublisher;
    }

    @Transactional
    public BookResponse create(CreateBookRequest request) {
        Book book = bookRepository.save(new Book(request.title(), request.author()));

        // Publish event — anyone can listen
        eventPublisher.publishEvent(new BookCreatedEvent(
            book.getId(), book.getTitle(), book.getAuthor()));

        return BookResponse.from(book);
    }
}

Listening to Events

@Component
public class BookEventListeners {

    @EventListener
    public void onBookCreated(BookCreatedEvent event) {
        log.info("Book created: {} by {}", event.title(), event.author());
    }
}

@Component
public class NotificationListener {

    private final EmailService emailService;

    @EventListener
    public void onBookCreated(BookCreatedEvent event) {
        emailService.sendNewBookNotification(event.title(), event.author());
    }
}

@Component
public class SearchIndexListener {

    @EventListener
    public void onBookCreated(BookCreatedEvent event) {
        searchService.indexBook(event.bookId());
    }
}

Three listeners, zero coupling — BookService doesn’t know any of them exist.


Async Events

@Component
public class NotificationListener {

    @Async
    @EventListener
    public void onBookCreated(BookCreatedEvent event) {
        // Runs in a separate thread
        emailService.sendNewBookNotification(event.title(), event.author());
    }
}

@TransactionalEventListener

Execute after the transaction commits — ensures data is actually saved:

@Component
public class SearchIndexListener {

    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void onBookCreated(BookCreatedEvent event) {
        // Only runs if the transaction committed successfully
        searchService.indexBook(event.bookId());
    }
}
Phase When
AFTER_COMMIT Transaction committed (default)
AFTER_ROLLBACK Transaction rolled back
AFTER_COMPLETION After commit or rollback
BEFORE_COMMIT Before commit

Built-in Spring Events

@EventListener
public void onApplicationReady(ApplicationReadyEvent event) {
    log.info("Application is ready!");
}

@EventListener
public void onContextClosed(ContextClosedEvent event) {
    log.info("Application shutting down");
}

Key Takeaways

  1. Events decouple publishers from listeners — add features without modifying existing code.
  2. ApplicationEventPublisher.publishEvent() to publish.
  3. @EventListener to listen — multiple listeners per event.
  4. @Async @EventListener for non-blocking event handling.
  5. @TransactionalEventListener(AFTER_COMMIT) — only runs after successful commit.

What’s Next?

Next: WebSocket — real-time bidirectional communication.

Updated: