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.

Java Architect Essentials
Java Architect Essentials
Java Architect Essentials
Mastering Spring Events: Synchronous & Asynchronous Usage with Code Samples

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 ms

Running the Demo

The complete source code and a runnable Spring Boot project are available at:

https://gitee.com/csps/mingyue-springboot-learning

backendJavaSpringAsynchronousSpringBootApplicationEvent
Java Architect Essentials
Written by

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.

0 followers
Reader feedback

How this landed with the community

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.