Application Events Interview Questions & Answers
Spring Boot application events interview guide — ApplicationEventPublisher, @EventListener, custom POJO events, synchronous vs @Async listeners, @TransactionalEventListener, conditional listening, ordering, and built-in lifecycle events.
It's an in-process publish/subscribe system built into the ApplicationContext. One bean publishes
an event; any number of listeners react to it — and the publisher has no reference to the listeners.
This decouples components: an OrderService can announce "order placed" without knowing that email, audit,
and inventory beans all care.
@Service
class OrderService {
private final ApplicationEventPublisher publisher;
OrderService(ApplicationEventPublisher p) { this.publisher = p; }
void place(Order o) {
save(o);
publisher.publishEvent(new OrderPlacedEvent(o.id())); // fire-and-forget
}
}
Rule of thumb: Application events are in-JVM pub/sub for decoupling beans — not a message broker and not cross-process.
Inject ApplicationEventPublisher and call publishEvent(...). Since Spring 4.2 the event can be
any plain object — it no longer has to extend ApplicationEvent. The publisher delivers it to every
matching listener.
record OrderPlacedEvent(long orderId) { } // plain POJO, no base class needed
publisher.publishEvent(new OrderPlacedEvent(42));
You can also publish from a non-bean by having the class implement ApplicationEventPublisherAware, but
constructor injection is the normal route.
Rule of thumb: Inject ApplicationEventPublisher, publish a POJO — no base class, no registration.
Annotate a method with @EventListener and declare the event type as its single parameter. Spring
registers the method as a listener and invokes it whenever a matching event is published. This replaced the
older ApplicationListener<T> interface for most use.
@Component
class EmailListener {
@EventListener
void on(OrderPlacedEvent e) { // parameter type = subscription
sendConfirmation(e.orderId());
}
}
One bean can have many @EventListener methods, each for a different event type.
Rule of thumb: @EventListener + the event type as the parameter is the modern way to subscribe.
Synchronous. publishEvent does not return until every listener has run on the publisher's thread.
The listeners execute sequentially, in the same call stack — so a slow listener slows the publisher, and an
exception in a listener propagates back to the publisher.
publisher.publishEvent(new OrderPlacedEvent(42));
// ⬆ blocks here until all @EventListener methods finish
This surprises people who expect "events" to be fire-and-forget — by default they're just decoupled method calls on the same thread.
Rule of thumb: Plain @EventListener is synchronous and in-thread — add @Async to make it run off
the publisher's thread.
Add @Async to the listener (and @EnableAsync somewhere). Now Spring hands the listener to a
TaskExecutor so it runs on a pool thread; publishEvent returns immediately and a slow or failing listener
no longer affects the publisher.
@Async
@EventListener
void on(OrderPlacedEvent e) { // runs on an executor, off the publisher thread
slowAuditWrite(e);
}
Note the trade-off: an async listener can't propagate exceptions back to the publisher and loses the caller's transaction/security context unless you propagate it deliberately.
Rule of thumb: @Async @EventListener decouples time as well as code — but you give up exception
propagation and the publisher's context.
Use the condition attribute with a SpEL expression evaluated against the event. The listener fires only
when the expression is true, letting you filter without an if inside the method.
@EventListener(condition = "#event.total > 1000")
void onLargeOrder(OrderPlacedEvent event) { // only high-value orders
flagForReview(event);
}
The root object is the event (referenceable as #event or #root.event). This keeps the listener focused
and avoids running it for irrelevant events.
Rule of thumb: Push event filtering into the condition SpEL so the listener body only runs when it
should.
A non-null return value is published as a new event — letting you build event chains where one listener's
output triggers the next. Returning null (or void) publishes nothing. A collection/array return publishes
each element as its own event.
@EventListener
ShipmentScheduledEvent on(OrderPlacedEvent e) {
return new ShipmentScheduledEvent(e.orderId()); // auto-published as a new event
}
It's a neat pattern but can make flow hard to trace — use sparingly.
Rule of thumb: A returned object from a listener becomes the next event — handy for chaining, easy to over-use.
For synchronous listeners of the same event, add @Order (or implement Ordered). Lower values run
first. Without it the order is undefined, so never rely on incidental ordering. Note ordering only makes
sense for synchronous listeners — async ones run concurrently.
@Order(1) @EventListener void validateFirst(OrderPlacedEvent e) { }
@Order(2) @EventListener void thenNotify(OrderPlacedEvent e) { }
Rule of thumb: Use @Order when sync listeners have a dependency; otherwise assume the order is
arbitrary.
It binds the listener to a transaction phase so it fires after the publishing transaction commits
(the default AFTER_COMMIT) rather than immediately. This solves a classic bug: with a plain
@EventListener, you publish "order placed", an async email goes out, then the transaction rolls back —
and you've emailed about an order that doesn't exist.
@TransactionalEventListener // default phase: AFTER_COMMIT
void on(OrderPlacedEvent e) {
sendConfirmation(e); // only runs if the tx actually committed
}
Other phases: BEFORE_COMMIT, AFTER_ROLLBACK, AFTER_COMPLETION.
Rule of thumb: When a listener's side effect must only happen if the data was really saved, use
@TransactionalEventListener(AFTER_COMMIT).
By default the listener is silently skipped — it only fires within a transaction's lifecycle, so a
no-transaction publish drops the event. To make it run anyway (treating "no transaction" as "already
committed"), set fallbackExecution = true.
@TransactionalEventListener(fallbackExecution = true)
void on(OrderPlacedEvent e) { ... } // also runs when there's no tx
This is a common "why isn't my listener firing in tests" gotcha — the test published outside any transaction.
Rule of thumb: A @TransactionalEventListener needs a transaction to fire; use fallbackExecution = true if it must also run without one.
Spring resolves listener generics, so you can have a single parameterized event type and subscribe to a
specific parameterization. If the event's generic type is erased at runtime, implement
ResolvableTypeProvider so Spring can still route it correctly.
class EntityChangedEvent<T> implements ResolvableTypeProvider {
final T entity;
EntityChangedEvent(T e) { this.entity = e; }
public ResolvableType getResolvableType() {
return ResolvableType.forClassWithGenerics(
getClass(), ResolvableType.forInstance(entity));
}
}
@EventListener
void onUser(EntityChangedEvent<User> e) { } // only fires for User events
Rule of thumb: Generic events route by type parameter — add ResolvableTypeProvider when erasure
would otherwise hide it.
The framework fires its own lifecycle events you can listen to. The most-used is ApplicationReadyEvent
(published when the app is fully started and ready to serve traffic) — ideal for startup tasks. Others
include ApplicationStartingEvent, ApplicationEnvironmentPreparedEvent, ContextRefreshedEvent, and
ApplicationFailedEvent.
@EventListener(ApplicationReadyEvent.class)
void warmCaches() { // runs once, when the app is ready
preloadReferenceData();
}
Rule of thumb: Hook ApplicationReadyEvent for "do this once on startup" — it fires after the context
and web server are up.
Use events when the publisher shouldn't know or care who reacts — when reactions are optional, plural, or cross-cutting (audit, notifications, cache eviction). Use a direct method call when there's one well-defined collaborator and you want compile-time clarity and easy tracing. Events trade explicitness for decoupling.
// Direct: explicit, traceable, tightly coupled
emailService.sendConfirmation(order);
// Event: decoupled, open to N reactions, harder to trace
publisher.publishEvent(new OrderPlacedEvent(order.id()));
Rule of thumb: One known collaborator → call it directly; many/optional/cross-cutting reactions → publish an event.
The exception propagates back to the publisher and aborts the remaining listeners — because they all
run on one thread in publishEvent. So a failing audit listener can break order placement. If you want
listeners to be isolated, make them @Async, or catch exceptions inside each listener.
@EventListener
void on(OrderPlacedEvent e) {
try { riskyWork(e); }
catch (Exception ex) { log.error("listener failed", ex); } // contain it
}
Rule of thumb: A throwing sync listener fails the publisher and stops later listeners — isolate
with @Async or local try/catch.
No. Application events are in-memory and single-JVM — they vanish on restart, don't cross process boundaries, offer no persistence, no delivery guarantees, and no consumer in another service. They're for decoupling beans inside one app. Cross-service, durable, or at-least-once delivery needs a real broker.
Application events: in-JVM, ephemeral, decouple beans
Kafka / RabbitMQ: cross-process, durable, guaranteed delivery
Rule of thumb: Reach for events inside a process, a broker between processes — never confuse the two.
Because it runs on a different thread. Spring's transaction and SecurityContextHolder state is
thread-bound by default, so an async listener starts with a clean slate — no active transaction, no
authenticated principal. You must propagate context explicitly (e.g. a DelegatingSecurityContextAsync TaskExecutor) or pass the data you need inside the event itself.
@Async @EventListener
void on(OrderPlacedEvent e) {
// SecurityContextHolder is EMPTY here unless the executor propagates it
}
Rule of thumb: Crossing to an async thread drops thread-bound context — carry what you need in the event payload or use a context-propagating executor.
More Async & Messaging interview questions
More ways to practice
The self-quiz is live. Join our channel for updates, new content & tech tips.