Backend Development 9 min read

Mastering Spring Event Mechanism: Custom Events, Listeners, Async & Ordering

This article introduces Spring's event mechanism, covering the creation of custom ApplicationEvent classes, implementing listeners via ApplicationListener and @EventListener annotations, publishing events, using @Async for asynchronous handling, configuring custom event multicaster, and controlling listener execution order with @Order, complete with code examples.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Mastering Spring Event Mechanism: Custom Events, Listeners, Async & Ordering

Environment: Spring 5.3.23

1. Introduction

Spring's event mechanism is a tool for handling internal or external events within the framework. It follows the observer design pattern and consists of three main parts: events, publishers, and listeners.

In Spring, an event is the core object; a publisher emits events, and a listener processes them. Event objects encapsulate the source and related information, enabling communication between the source and listeners. Publishers are typically injected via ApplicationEventPublisher, while listeners implement the ApplicationListener interface.

The Spring container itself publishes many events and also supports custom events. In Spring Boot, you can publish events using ApplicationEventPublisher and define custom events by extending ApplicationEvent. Listeners are defined by implementing ApplicationListener.

Overall, Spring's event mechanism is a useful tool for managing and handling system events.

2. Event Publishing and Listening

Creating and publishing a custom event by extending ApplicationEvent :

<code>static class OrderEvent extends ApplicationEvent {
  private Order order;
  public OrderEvent(Object source, Order order) {
    super(source);
    this.order = order;
  }
  public Order getOrder() {
    return this.order;
  }
}</code>

Implementing a listener for the custom event:

<code>static class OrderListener implements ApplicationListener<OrderEvent> {
  @Override
  public void onApplicationEvent(OrderEvent event) {
    System.out.printf("Listener received event: %s", event.getOrder().toString());
  }
}</code>

Publishing the custom event via ApplicationEventPublisher :

<code>static class OrderService implements ApplicationEventPublisherAware {
  private ApplicationEventPublisher eventPublisher;
  @Override
  public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) {
    this.eventPublisher = eventPublisher;
  }
  public void save(Order order) {
    this.eventPublisher.publishEvent(new OrderEvent("create order event", new Order()));
  }
}</code>

Annotation‑based listener using @EventListener :

<code>static class MyEventListener {
  @EventListener
  public void receiveOrderEvent(OrderEvent event) {
    System.out.printf("Listener received event: %s", event.getOrder().toString());
  }
}</code>

Listening to multiple events:

<code>@EventListener({OrderEvent.class, UserEvent.class})
public void receiveOrderEvent() {
  System.out.println("Listener received defined event");
}</code>

Using ApplicationEvent as a generic parameter:

<code>@EventListener({OrderEvent.class, UserEvent.class})
public void receiveOrderEvent(ApplicationEvent event) {
  System.out.printf("Listener received event: %s", event.getSource());
}</code>

Filtering events with SpEL expressions:

<code>// User object
static class User {
  private Integer type;
  public User(Integer type) { this.type = type; }
}
// UserEvent extending ApplicationEvent
static class UserEvent extends ApplicationEvent {
  private User user;
  public UserEvent(Object source, User user) {
    super(source);
    this.user = user;
  }
  public User getUser() { return user; }
}
// Listener that only triggers when user.type == 1
@EventListener(condition = "#event.user.type == 1")
public void receiveOrderEvent(UserEvent event) {
  System.out.printf("Listener received event: %s", event.getUser());
}</code>

3. Asynchronous Event Listening

Method 1: Use @Async on an @EventListener method:

<code>@EventListener
@Async
public void receiveOrderEvent(UserEvent event) {
  System.out.printf("%s - Listener received event: %s", Thread.currentThread().getName(), event.getUser());
}</code>

Result example:

<code>SimpleAsyncTaskExecutor-1 - Listener received event: com.pack.main.events.EventMain$User@abb3dc</code>

Method 2: Define a custom event multicaster with a dedicated executor:

<code>@Bean
public SimpleApplicationEventMulticaster applicationEventMulticaster() {
  SimpleApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster();
  multicaster.setTaskExecutor(Executors.newSingleThreadExecutor());
  return multicaster;
}</code>

Spring will automatically register this multicaster during container initialization. Example execution result:

<code>pool-1-thread-1 - Listener received event: com.pack.main.events.EventMain$User@50f8a6</code>

Important notes for asynchronous listeners:

If an async listener throws an exception, it will not propagate to the caller; handle it via AsyncUncaughtExceptionHandler .

Async listener methods cannot return values to trigger subsequent events; manually inject ApplicationEventPublisher if you need to publish further events.

4. Event Listener Order

Use the @Order annotation to define execution precedence; lower values have higher priority:

<code>@EventListener
@Order(1)
public void receiveOrderEvent1(UserEvent event) {
  System.out.printf("%s - Listener received event - 1: %s%n", Thread.currentThread().getName(), event.getUser());
}
@EventListener
@Order(0)
public void receiveOrderEvent2(UserEvent event) {
  System.out.printf("%s - Listener received event - 2: %s%n", Thread.currentThread().getName(), event.getUser());
}</code>

Execution result demonstrates that the method with @Order(0) runs before the one with @Order(1) :

<code>main - Listener received event - 2: com.pack.main.events.EventMain$User@96bacf
main - Listener received event - 1: com.pack.main.events.EventMain$User@96bacf</code>

Done!!!

backendJavaSpringasynchronouseventannotation
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.