Application Events
Decouple components in-process by publishing and listening for events - the gateway to event-driven design.
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.
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.
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.