Spring's Hidden Async Alternative to MQ: Built-in Events for In-Process Decoupling

This article explains how Spring's built-in event mechanism provides a lightweight alternative to message queues for in-process asynchronous decoupling, covering synchronous vs asynchronous handling, transactional event listeners, thread pool configuration, lifecycle events, and when persistent solutions are needed.

Java Architect Essentials
Java Architect Essentials
Java Architect Essentials
Spring's Hidden Async Alternative to MQ: Built-in Events for In-Process Decoupling

When an order placement method accumulates SMS, loyalty points, logging, and other side effects, every requirement change forces modifications to the core flow. The author recommends first distinguishing two scenarios: decoupling modules within the same process can use Spring Event; tasks that must not be lost require persistence and failure recovery.

Spring Event Basics

An event object carries data, ApplicationEventPublisher publishes it, the multicaster finds matching listeners, and @EventListener handles it. The event can be a plain object — it does not need to extend ApplicationEvent.

Spring应用内部通过事件把不同处理模块连接起来
Spring应用内部通过事件把不同处理模块连接起来

The default multicaster invokes listeners synchronously: the publishing thread waits until all listeners finish, and an unhandled exception in a listener can interrupt the publishing flow. Simply extracting classes does not prevent the SMS call from slowing down order placement.

Making It Asynchronous

To run listeners asynchronously, enable @EnableAsync, make the listener a Spring bean, and annotate the handler method with @Async specifying a thread pool (e.g., eventExecutor).

Order Submission Then Notification

The core example uses JDK 17 and Spring Framework 6.2.12. Besides async configuration, Spring transaction management must be enabled ( @EnableTransactionManagement in plain Spring; verify it works in Spring Boot) and a ThreadPoolTaskExecutor named eventExecutor must be configured.

record OrderCreated(long id) {}

@Component
class OrderNotice {
    @Async("eventExecutor")
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void on(OrderCreated event) {
        System.out.println("已提交订单:" + event.id());
    }
}

Inside the active order transaction, after saving, call publisher.publishEvent(new OrderCreated(id)). The event should carry only the necessary ID or an immutable snapshot — avoid sharing mutable entities across threads. AFTER_COMMIT is the default phase; the listener does not run if the transaction rolls back. If publishing occurs without a transaction, the listener also does not run by default. Setting fallbackExecution=true allows execution without a transaction, but it does not recreate the "commit succeeded" guarantee.

事务成功提交后才向异步线程池安排通知,回滚和无事务默认不执行
事务成功提交后才向异步线程池安排通知,回滚和无事务默认不执行

Thread Changed — Failure Handling Still Needed

The thread pool must have bounded capacity and a rejection policy. Exceptions in async methods do not propagate back to the publisher; configure an AsyncUncaughtExceptionHandler. If task submission is rejected (queue full), a try-catch inside the listener method cannot catch it. @Order only affects the scheduling order of listeners, not the completion order of async tasks. The new thread does not inherit the original transaction boundary; any database writes need their own transaction demarcation.

Deleting cache after commit avoids some timing issues, but deletion can still fail. Deleting before commit risks concurrent reads repopulating stale values. Do not label all these cases as cache penetration.

Startup Events — Know the Trigger

Spring容器刷新、启动、停止和关闭对应的事件名称
Spring容器刷新、启动、停止和关闭对应的事件名称

The arrows in the diagram do not represent a fixed sequence for every Boot startup. Refresh, explicit start(), stop(), and close are distinct operations; in parent-child container hierarchies, events are not guaranteed to fire exactly twice.

Boot's ApplicationReadyEvent signals the application has reached the ready phase, which differs from ContextRefreshedEvent. Where to place warm-up logic depends on whether it must complete before traffic arrives.

When Persistent Solutions Are Required

按同进程解耦、跨服务通信和可靠性需要判断事件方案
按同进程解耦、跨服务通信和可靠性需要判断事件方案

Spring Event has no built-in persistence, consumption acknowledgment, or reliable redelivery. If the process crashes after transaction commit but before the async notification is sent, the notification is lost — adding @Async does not recover it.

For cross-service communication or tasks that cannot be lost, consider MQ, a persistent task table, or the outbox pattern, supplemented with delivery acknowledgment, retries, and idempotency. Using MQ alone does not automatically guarantee reliability.

Critical validations such as inventory, payment verification, or risk control that determine whether an order can be created must not be moved to the background. Start with non-critical notifications that allow remedial handling, build failure recording and remedial processes, then decide if a persistent pipeline is necessary.

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.

Asynchronous ProgrammingSpring FrameworkSpring EventsThread Pool ConfigurationApplication Lifecycle EventsIn-Process DecouplingMessage Queue AlternativesTransactional Event Listeners
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.