What is the difference between Spring's ApplicationEventPublisher / @EventListener and the messaging support in spring-messaging (Message / MessageChannel)?
answer
- @EventListener = in-JVM pub/sub, same thread + tx
- spring-messaging = Message/MessageChannel, broker-backed, cross-process
- events not durable, not serialized
- @TransactionalEventListener bridges to broker
- sync by default, not async
basics
~10 sApplicationEventPublisher with @EventListener sends events between beans inside one running JVM. spring-messaging (Message, MessageChannel) is a broader abstraction for passing messages, often across process boundaries through a broker like Kafka or RabbitMQ.
solid answer
~40 s@EventListener plus ApplicationEventPublisher is the ApplicationContext's in-process pub/sub: a bean publishes a plain object, other beans in the same JVM handle it, synchronously by default, sharing the caller's thread and transaction. Nothing leaves the process and nothing is serialized. spring-messaging is a generic messaging abstraction — Message<T> (payload + headers), MessageChannel, MessageHandler — that underpins Spring Integration, STOMP/WebSocket, and Spring Cloud Stream, and is typically wired to a real broker so messages cross process boundaries, are serialized, and can be durable. Rule of thumb: use application events for decoupling components within one app; reach for messaging/a broker when the consumer is a separate process or you need durability, buffering, or delivery guarantees. @TransactionalEventListener is the common bridge between the two worlds.
code
java · 20 lines// In-process application event
record OrderPlaced(String orderId) {}
@Service
class OrderService {
private final ApplicationEventPublisher publisher;
OrderService(ApplicationEventPublisher publisher) { this.publisher = publisher; }
@Transactional
public void place(String id) {
// ... persist order ...
publisher.publishEvent(new OrderPlaced(id)); // in-JVM, same thread + tx
}
}
@Component
class InventoryListener {
@EventListener // synchronous by default; runs in OrderService's transaction
void on(OrderPlaced e) { /* same JVM, no serialization */ }
}go deeper
Know one is inside the app (events), the other can go between apps via a broker (messaging).
Add that events are synchronous, share the transaction, and are not serialized or durable.
Articulate the decision boundary and name @TransactionalEventListener as the bridge to a broker.
Frame it as coupling/lifecycle strategy and note the dual-write reliability limit that motivates an outbox.
## Two different tools that look similar Spring gives you two ways to send a notification from producer code to consumer code without them calling each other directly. They are easy to confuse but solve different problems. ### 1. In-process application events - **`ApplicationEventPublisher`** — an interface every Spring bean can inject (the `ApplicationContext` implements it). You call `publishEvent(someObject)`. - **`@EventListener`** — put on any bean method; the method's single parameter type selects which events it receives. (Older style: implement `ApplicationListener<T>`.) - Since Spring 4.2 the event can be **any object** — it no longer has to extend `ApplicationEventPublisher`'s legacy `ApplicationEvent`. **Key semantics:** - **Synchronous by default.** `publishEvent` does not return until every listener has run. Listeners execute on the **same thread** and inside the **same transaction** as the publisher. - **In-JVM only.** The event object is passed by reference — no serialization, no network. If the JVM crashes, the event is gone. - Ordering via `@Order`; async via `@Async` on the listener (needs `@EnableAsync`) or by configuring the `ApplicationEventMulticaster` with a `TaskExecutor`. ### 2. spring-messaging abstraction The `spring-messaging` module defines broker-neutral types: - **`Message<T>`** — a payload (`getPayload()`) plus a `MessageHeaders` map. - **`MessageChannel`** — something you `send(Message)` to; a `SubscribableChannel` delivers to `MessageHandler`s. - **`MessageHandler`** — receives messages. These types are the foundation for **Spring Integration**, **STOMP messaging over WebSocket** (`SimpMessagingTemplate`), and **Spring Cloud Stream**. In real systems the channel is backed by a **broker** (Kafka, RabbitMQ, JMS), so messages leave the process, are **serialized**, can be **buffered and made durable**, and can be consumed by a **different application**. ## How they relate — the mental model - Application events = wiring **inside** one deployable. - Broker-backed messaging = wiring **between** deployables (or for durability/async offloading even within one). - Both are pub/sub, but only messaging crosses the process boundary and survives a restart. ## When to cross the process boundary Stay with application events when: the handler lives in the same app, you want to decouple modules, you don't need durability, and eventual work can happen in-thread or via `@Async`. Move to a broker when: the consumer is a separate service, you need the message to survive a crash, you need back-pressure/buffering, ret/replay, or fan-out to many independent consumers, or you must decouple deployment lifecycles. ## The bridge: @TransactionalEventListener A very common pattern is to keep the producer decoupled by publishing an **in-process** application event, and have a `@TransactionalEventListener` (phase `AFTER_COMMIT`) turn that event into a **broker** message once the database transaction that produced it has committed. This avoids sending a message about data that later rolls back. The dual-write reliability caveat (DB commit succeeds, broker publish fails) is what pushes serious systems toward the transactional-outbox pattern. ## Gotchas - People assume `@EventListener` is async — it is **not** by default. - People assume application events are durable — they are **not**; a crash loses them. - `Message`/`MessageChannel` by themselves are just abstractions; without a broker binder they are still in-JVM (e.g. a plain `DirectChannel`).
- Is publishEvent synchronous or asynchronous by default?Synchronous. It blocks until all listeners finish, on the publisher's thread and within its transaction. You make it async with @Async on the listener (plus @EnableAsync) or by giving the ApplicationEventMulticaster a TaskExecutor.
- If a listener must notify a separate microservice, which approach fits?A broker-backed message (spring-messaging via Spring Cloud Stream / Kafka / RabbitMQ), because the consumer is another process and you need serialization and durability — application events cannot leave the JVM.
saying these in an interview costs you the question
- Claiming @EventListener is asynchronous by default
- Thinking application events are durable / survive a JVM crash
- Believing ApplicationEventPublisher can deliver to another process/service
- Saying Message/MessageChannel always means a network broker