Not every decoupling problem needs a message broker. Spring's application events let one component publish a fact and others react — all in-process, all type-safe, with no infrastructure to run.
Define and publish an event
An event is just an immutable object. Publish it through the
ApplicationEventPublisher.
public record OrderPlaced(Long orderId, BigDecimal total) {}
@Service
public class OrderService {
private final ApplicationEventPublisher events;
@Transactional
public void place(Order order) {
repository.save(order);
events.publishEvent(new OrderPlaced(order.getId(), order.getTotal()));
}
}Listen without coupling
Any bean can react with @EventListener. The publisher has no idea who's
listening, which keeps concerns separated.
@Component
public class Listeners {
@EventListener
public void sendReceipt(OrderPlaced event) {
email.sendReceipt(event.orderId());
}
@EventListener
public void updateMetrics(OrderPlaced event) {
metrics.recordSale(event.total());
}
}Wait for the commit
Reacting before the transaction commits risks acting on data that gets rolled
back. @TransactionalEventListener fires only after a successful commit.
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void onCommitted(OrderPlaced event) {
analytics.track(event);
}Takeaways
- Application events decouple components without extra infrastructure.
- Multiple listeners can react to one event independently.
- Use
@TransactionalEventListenerto act only after a commit succeeds.
When listeners need durability, retries, or cross-service delivery, that's your signal to graduate to Kafka or RabbitMQ.