Application events inside one service
Decouple beans with published events, and fire side effects only after the commit.
Open this lesson in the learning hubKey points
- Publish with
ApplicationEventPublisher. Any bean with an@EventListenerfor that type receives it. - A plain listener is synchronous: it runs on the publishing thread, inside the same transaction.
- So an email sent from a plain listener can go out for an order whose transaction then rolls back.
@TransactionalEventListener(phase = AFTER_COMMIT)waits until the data is actually committed.- Add
@Asyncto move a listener onto another thread - it then cannot affect the publisher at all. - Events are in-process only. Crossing a service boundary needs a broker, not an ApplicationEvent.
Example
public record OrderPlaced(long orderId, String customerEmail) { }
@Service
class CheckoutService {
private final ApplicationEventPublisher events;
CheckoutService(ApplicationEventPublisher events) { this.events = events; }
@Transactional
public void checkout(Cart cart) {
Order order = orders.save(Order.from(cart));
events.publishEvent(new OrderPlaced(order.getId(), order.getEmail()));
// still inside the transaction here - nothing is committed yet
}
}
@Component
class ReceiptListener {
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
void onOrderPlaced(OrderPlaced event) {
mail.sendReceipt(event.customerEmail()); // the order really exists now
}
@EventListener // runs BEFORE the commit
void audit(OrderPlaced event) {
audits.record(event); // fine: it shares the same rollback
}
}
Publish inside the transaction, but do anything the outside world can see only after the commit.
This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Spring Boot course, and every lesson in it is listed on the Spring Boot contents page.