Using Spring Application Events for Synchronous and Asynchronous Business Logic

This article demonstrates how to leverage Spring's ApplicationEvent mechanism to decouple business logic, showing both synchronous and asynchronous event publishing, custom event definitions, listener implementations, unit testing, and enabling async processing with @EnableAsync and @Async annotations.

Top Architect
Top Architect
Top Architect
Using Spring Application Events for Synchronous and Asynchronous Business Logic

In real-world business development, complex logic often involves a core operation plus many sub‑tasks, which can lead to long, tightly‑coupled code. Some tasks, such as sending emails or SMS, do not need to complete within the same request, and using a heavyweight message queue may be unnecessary.

Spring Event (Application Event) provides a lightweight observer‑pattern solution. After a bean finishes its work, it can publish an event that other beans listen to, allowing synchronous or asynchronous handling without tight coupling.

Custom Event Definition

import lombok.Data;
import lombok.AllArgsConstructor;
import org.springframework.context.ApplicationEvent;

/**
 * Custom event carrying order information.
 */
@Data
@AllArgsConstructor
public class OrderProductEvent extends ApplicationEvent {
    private String orderId;
    public OrderProductEvent(Object source, String orderId) {
        super(source);
        this.orderId = orderId;
    }
}

Listener Implementation (Synchronous)

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 processing
        long end = System.currentTimeMillis();
        log.info("{}: validate order price took {} ms", orderId, (end - start));
    }
}

Publisher (Service Layer)

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 price synchronously
        applicationContext.publishEvent(new OrderProductEvent(this, orderId));
        // 3. async tasks such as sending SMS/email
        applicationContext.publishEvent(new MsgEvent(orderId));
        long end = System.currentTimeMillis();
        log.info("All tasks completed, total time: {} ms", (end - start));
        return "Purchase successful";
    }
}

Asynchronous Listener

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 async work
        long end = System.currentTimeMillis();
        log.info("{}: SMS & email took {} ms", orderId, (end - start));
    }
}

Enable Asynchronous Execution

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);
    }
}

Unit Test

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");
    }
}

Running the test shows the synchronous listener logging the price‑validation duration (~2 seconds) and the asynchronous listener logging SMS/email sending after the main thread finishes, demonstrating how Spring Event can simplify both sync and async workflows.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaspringasyncEventListenerApplicationEvent
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn 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.