Mastering Spring Events: Synchronous & Asynchronous Usage with Code Samples
This article explains how to use Spring ApplicationEvent to decouple business logic, showing step‑by‑step code for defining custom events, creating listeners with both ApplicationListener and @EventListener, publishing events synchronously and asynchronously, enabling async processing, and verifying behavior with unit tests.
Overview
Spring ApplicationEvent implements a lightweight observer pattern that lets beans publish events and other beans listen to them without the overhead of a full message queue. It is especially useful for business scenarios where certain actions (e.g., sending email or SMS) can be performed asynchronously.
Synchronous Event Example
Define a custom event class that extends ApplicationEvent:
import lombok.Data;
import lombok.ToString;
import org.springframework.context.ApplicationEvent;
@Data
@ToString
public class OrderProductEvent extends ApplicationEvent {
/** payload */
private String orderId;
public OrderProductEvent(Object source, String orderId) {
super(source);
this.orderId = orderId;
}
}Create a listener that implements ApplicationListener<OrderProductEvent> (or use @EventListener) to process the event synchronously:
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class OrderProductListener implements ApplicationListener<OrderProductEvent> {
@SneakyThrows
@Override
public void onApplicationEvent(OrderProductEvent event) {
String orderId = event.getOrderId();
long start = System.currentTimeMillis();
Thread.sleep(2000); // simulate time‑consuming validation
long end = System.currentTimeMillis();
log.info("{}: validate order product price took {} ms", orderId, (end - start));
}
}Publish the event from a service method using ApplicationContext.publishEvent:
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
@Slf4j
@Service
@RequiredArgsConstructor
public class OrderService {
private final ApplicationContext applicationContext;
public String buyOrder(String orderId) {
long start = System.currentTimeMillis();
// 1. query order details (omitted)
// 2. validate order price synchronously
applicationContext.publishEvent(new OrderProductEvent(this, orderId));
// 3. placeholder for asynchronous SMS/email
long end = System.currentTimeMillis();
log.info("All tasks completed, total time {} ms", (end - start));
return "purchase successful";
}
}Asynchronous Event Example
Define a second event for asynchronous processing:
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class MsgEvent {
private String orderId;
}Implement a listener that handles the event on a separate thread by combining @EventListener with @Async:
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class MsgListener {
@SneakyThrows
@Async
@EventListener(MsgEvent.class)
public void sendMsg(MsgEvent event) {
String orderId = event.getOrderId();
long start = System.currentTimeMillis();
log.info("Sending SMS");
log.info("Sending Email");
Thread.sleep(4000); // simulate I/O latency
long end = System.currentTimeMillis();
log.info("{}: SMS & Email took {} ms", orderId, (end - start));
}
}Enable asynchronous execution in the Spring Boot entry point:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
@EnableAsync
@SpringBootApplication
public class MingYueSpringbootEventApplication {
public static void main(String[] args) {
SpringApplication.run(MingYueSpringbootEventApplication.class, args);
}
}Publishing Both Events
Update OrderService to publish the asynchronous MsgEvent after the synchronous validation:
public String buyOrder(String orderId) {
long start = System.currentTimeMillis();
// 1. query order details (omitted)
// 2. synchronous validation
applicationContext.publishEvent(new OrderProductEvent(this, orderId));
// 3. asynchronous notification
applicationContext.publishEvent(new MsgEvent(orderId));
long end = System.currentTimeMillis();
log.info("All tasks completed, total time {} ms", (end - start));
return "purchase successful";
}Unit Test
A simple JUnit test invokes the service method and demonstrates that the main thread finishes before the async listener completes:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class OrderServiceTest {
@Autowired
private OrderService orderService;
@Test
public void buyOrderTest() {
orderService.buyOrder("732171109");
}
}Sample Log Output
When the test runs, the console shows two distinct thread names:
2022-04-24 10:13:17.535 INFO [main] OrderProductListener : 732171109: validate order product price took 2008 ms
2022-04-24 10:13:17.536 INFO [main] OrderService : All tasks completed, total time 2009 ms
2022-04-24 10:30:59.028 INFO [task-1] MsgListener : Sending SMS
2022-04-24 10:30:59.028 INFO [task-1] MsgListener : Sending Email
2022-04-24 10:30:59.028 INFO [task-1] MsgListener : 732171109: SMS & Email took 4002 msRunning the Demo
The complete source code and a runnable Spring Boot project are available at:
https://gitee.com/csps/mingyue-springboot-learning
Java Architect Essentials
Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
