Start Learning
Javaneer
Back to stage
Module 6·Reactive & Messaging

Application Events

Decouple components in-process by publishing and listening for events - the gateway to event-driven design.

16 min readIntermediate
On this page

Before reaching for a message broker like Kafka, Spring gives you event-driven design inside a single application, for free. Application events let one component announce that something happened, and other components react - without the two ever referencing each other. It's the gateway drug to event-driven architecture, and often all you need.

Publish and listen

When BookVault records a loan, several things might need to happen: send a confirmation, update a "popular books" counter, log an audit entry. Rather than the LendingService calling each of those directly (coupling it to all of them), it publishes an event:

record BookBorrowedEvent(String isbn, Long memberId) {}

@Service
class LendingService {
    private final ApplicationEventPublisher events;

    Loan borrow(String isbn, Long memberId) {
        // ... reserve a copy, save the loan ...
        events.publishEvent(new BookBorrowedEvent(isbn, memberId));  // announce it
        return loan;
    }
}

Interested components just listen:

@Component
class BorrowNotifier {
    @EventListener
    void onBorrow(BookBorrowedEvent event) {
        // send a "you borrowed a book" notification
    }
}

The LendingService has no idea who's listening - you can add a new listener tomorrow without touching it at all.

A notice board, not a phone tree

Coupling the borrow logic to every follow-up action is like a phone tree: the first person must know and call everyone, and adding a person means editing the list. Events are a notice board: you pin up "a book was borrowed," and anyone who cares reads it and acts. The publisher never needs to know who's watching - new subscribers just start reading the board.

Tie events to the transaction

There's a subtlety with events fired inside a database transaction. If you notify a member "you borrowed a book" but the transaction then rolls back, you've lied to them. The fix is @TransactionalEventListener, which runs the listener only after the transaction commits:

@Component
class BorrowNotifier {
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    void onBorrow(BookBorrowedEvent event) {
        // guaranteed the loan really was committed before we notify
    }
}

Pair it with @Async and the notification runs after commit and off the request thread - exactly what BookVault does for its borrow notifications.

In-process first, broker later

Application events decouple components within one app; a broker (Kafka, RabbitMQ) decouples separate services across the network. The programming model is similar, so events are the perfect place to learn event-driven thinking before you distribute it. When BookVault later splits into services, that in-process event becomes a message on a topic.

Notify only on real borrows

BookVault publishes a BookBorrowedEvent inside the transactional borrow method, and a listener emails the member. Occasionally a borrow fails after the event is prepared and the transaction rolls back - yet members still got emails. Which listener annotation fixes this, and why?

What do Spring application events achieve?

Key takeaways

  • Application events decouple components in-process: publish with ApplicationEventPublisher, react with @EventListener.
  • The publisher never references its listeners, so you add new reactions without changing it.
  • @TransactionalEventListener(AFTER_COMMIT) runs a listener only after the transaction commits - so you never act on a rolled-back change.
  • Combine it with @Async to handle the event after commit and off the request thread.
  • In-process events are the natural first step toward broker-based messaging between services.
Was this lesson helpful?
Edit this page on GitHub