Mastering Spring’s Observer Pattern: From Basics to Asynchronous Event Handling

This article explains the Observer pattern, its core roles and typical use cases, then dives into Spring's event mechanism—including ApplicationEvent, ApplicationListener, and ApplicationEventPublisher—showing how to configure synchronous and asynchronous event processing with code examples.

JD Cloud Developers
JD Cloud Developers
JD Cloud Developers
Mastering Spring’s Observer Pattern: From Basics to Asynchronous Event Handling

Observer Pattern Overview

The Observer pattern defines a one‑to‑many dependency between objects so that when the state of a subject changes, all its observers are automatically notified and updated. It is also known as publish‑subscribe or model‑view.

Key Roles

Subject (Observable) : declares the responsibilities that observers must implement, can dynamically add or remove observers, and notifies them.

Observer : receives notifications and performs update logic.

ConcreteSubject : implements the subject’s business logic and decides which events to publish.

ConcreteObserver : contains specific handling logic for received events.

Typical Scenarios

Objects have a one‑to‑many relationship where a state change in one affects many.

Two aspects of a model depend on each other and need independent reuse.

Broadcast‑style communication where the sender does not need to know the concrete listeners.

Layered or chained triggers that require cross‑domain notifications.

Observer Pattern in Spring

Spring Event Listening Mechanism

Spring implements the Observer pattern through its event system. An ApplicationEvent (extending EventObject) represents an event, while ApplicationListener (extending EventListener) defines the listener interface. Beans that implement ApplicationListener are automatically registered with the ApplicationEventMulticaster.

public abstract class ApplicationEvent extends EventObject {
    private static final long serialVersionUID = 7099057708183571937L;
    private final long timestamp = System.currentTimeMillis();
    public ApplicationEvent(Object source) {
        super(source);
    }
    public final long getTimestamp() {
        return this.timestamp;
    }
}
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
    void onApplicationEvent(E event);
}

The ApplicationEventPublisher interface is implemented by ApplicationContext. The core publishing method in AbstractApplicationContext is:

protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
    Assert.notNull(event, "Event must not be null");
    ApplicationEvent applicationEvent;
    if (event instanceof ApplicationEvent) {
        applicationEvent = (ApplicationEvent) event;
    } else {
        applicationEvent = new PayloadApplicationEvent<>(this, event);
        if (eventType == null) {
            eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
        }
    }
    // ... register early events or multicast directly ...
    getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
    // propagate to parent context if present
    if (this.parent != null) {
        if (this.parent instanceof AbstractApplicationContext) {
            ((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
        } else {
            this.parent.publishEvent(event);
        }
    }
}

The ApplicationEventMulticaster delivers events to all matching listeners. If an Executor is configured, delivery can be asynchronous:

public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
    ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
    Executor executor = getTaskExecutor();
    for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
        if (executor != null) {
            executor.execute(() -> invokeListener(listener, event));
        } else {
            invokeListener(listener, event);
        }
    }
}

Initialization in AbstractApplicationContext

During context refresh, Spring initializes the event multicaster:

protected void initApplicationEventMulticaster() {
    ConfigurableListableBeanFactory beanFactory = getBeanFactory();
    if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
        this.applicationEventMulticaster = beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
    } else {
        this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
        beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
    }
}

Listeners are registered via registerListeners(), which adds both bean‑defined listeners and those discovered by type.

protected void registerListeners() {
    for (ApplicationListener<?> listener : getApplicationListeners()) {
        getApplicationEventMulticaster().addApplicationListener(listener);
    }
    String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
    for (String listenerBeanName : listenerBeanNames) {
        getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
    }
    // process early events if any
    Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
    this.earlyApplicationEvents = null;
    if (!CollectionUtils.isEmpty(earlyEventsToProcess)) {
        for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
            getApplicationEventMulticaster().multicastEvent(earlyEvent);
        }
    }
}

Asynchronous Event Handling

To enable async processing, provide a custom SimpleApplicationEventMulticaster with a configured TaskExecutor:

@Configuration
@EnableAsync
public class Config {
    @Bean(AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME)
    public SimpleApplicationEventMulticaster simpleApplicationEventMulticaster() {
        SimpleApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster();
        multicaster.setTaskExecutor(taskExecutor());
        return multicaster;
    }
    @Bean
    public TaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(20);
        executor.setQueueCapacity(100);
        executor.setKeepAliveSeconds(300);
        executor.setThreadNamePrefix("thread-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
        executor.setWaitForTasksToCompleteOnShutdown(true);
        return executor;
    }
}

Alternatively, the same configuration can be expressed in XML by defining a SimpleApplicationEventMulticaster bean and injecting a ThreadPoolTaskExecutor as its taskExecutor property.

Conclusion

The article covered the fundamentals of the Observer pattern, its roles, and typical use cases, then demonstrated how Spring implements this pattern through its event mechanism. By default Spring events are synchronous, but configuring a custom multicaster with a task executor enables efficient asynchronous processing.

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 processingObserver PatternapplicationcontextEvent PublishingSpring Events
JD Cloud Developers
Written by

JD Cloud Developers

JD Cloud Developers (Developer of JD Technology) is a JD Technology Group platform offering technical sharing and communication for AI, cloud computing, IoT and related developers. It publishes JD product technical information, industry content, and tech event news. Embrace technology and partner with developers to envision the future.

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.