Mastering Spring Events: Definition, Benefits, Usage, and Deep Source‑Code Walkthrough

Spring Event provides a decoupled, extensible mechanism for publishing and handling application events in Spring, covering its definition, benefits, drawbacks, practical usage with code examples, core internal classes, reserved events, asynchronous processing options, and detailed source‑code analysis of the event multicaster and listener registration.

Ximalaya Technology Team
Ximalaya Technology Team
Ximalaya Technology Team
Mastering Spring Events: Definition, Benefits, Usage, and Deep Source‑Code Walkthrough

What is a Spring Event?

A Spring ApplicationEvent is an object created when a notable action occurs (e.g., a button click or an HTTP request). The event is published by an ApplicationEventPublisher and consumed by one or more ApplicationListener implementations or methods annotated with @EventListener. This decouples the event source from its handlers.

Key Benefits

Reduces coupling between components.

Improves scalability – new listeners can be added without changing the publisher.

Enables code reuse; the same listener can handle multiple events.

Supports asynchronous handling via an Executor.

Facilitates testing by allowing events to be simulated.

Potential Drawbacks

Excessive use can increase code complexity.

High event volume may impact performance.

Listener execution order is not guaranteed.

Asynchronous flow can make debugging harder.

Defining and Publishing a Custom Event

Create a class that extends ApplicationEvent and add any payload fields.

public class FinishLvEvent extends ApplicationEvent {
    private String result;
    private Object response;

    public FinishLvEvent(Object source, String result, Object response) {
        super(source);
        this.result = result;
        this.response = response;
    }
    // getters & setters omitted
}

Inject ApplicationEventPublisher (the Spring container itself implements this interface) and call publishEvent:

@Service
public class AuditService {
    @Autowired
    private ApplicationEventPublisher publisher;

    public void audit(int status, long id) {
        // update DB …
        publisher.publishEvent(new AuditEvent(this, status, id));
    }
}

Listening to Events

Two approaches are common:

Implement ApplicationListener

@Component
public class AuditEventListener implements ApplicationListener<AuditEvent> {
    @Override
    public void onApplicationEvent(AuditEvent event) {
        // handle event
    }
}

Use @EventListener on a method

@Component
public class EventProcessor {
    @EventListener(classes = CustomEvent.class, condition = "#event.status == 1")
    public void process(CustomEvent event) {
        // handle event
    }
}

The classes attribute filters the event type, while condition evaluates a SpEL expression against the event instance.

Core Spring Event Infrastructure

ApplicationEvent – abstract base class for all events.

ApplicationListener<E> – interface for listener beans.

ApplicationEventPublisher – publishes events.

ApplicationEventMulticaster – dispatches events to matching listeners.

SimpleApplicationEventMulticaster – default synchronous multicaster; can be supplied with an Executor for async dispatch.

AsyncApplicationEventMulticaster – dedicated asynchronous implementation.

EventListenerMethodProcessor – scans beans for @EventListener methods and registers them as listeners.

Pre‑defined (Framework) Events

ContextRefreshedEvent

– published after the ApplicationContext is initialized or refreshed. ContextStartedEvent – published when ConfigurableApplicationContext.start() is invoked. ContextStoppedEvent – published on stop(). ContextClosedEvent – published when the context is closed. RequestHandledEvent / ServletRequestHandledEvent – published after a web request is processed. SessionDestroyedEvent – published when an HTTP session ends.

Event Publishing Flow in Spring

The container registers itself as an ApplicationEventPublisher during bean‑factory preparation:

// AbstractApplicationContext#prepareBeanFactory
beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);

When publishEvent is called, the following steps occur:

Non‑ ApplicationEvent objects are wrapped in PayloadApplicationEvent.

If listeners have not yet been registered, the event is stored in earlyApplicationEvents.

Once the ApplicationEventMulticaster is ready, the event is dispatched via multicastEvent.

The event is also propagated to any parent context.

protected void publishEvent(Object event, @Nullable ResolvableType type) {
    ApplicationEvent appEvent = (event instanceof ApplicationEvent) ?
            (ApplicationEvent) event : new PayloadApplicationEvent<>(this, event);
    if (this.earlyApplicationEvents != null) {
        this.earlyApplicationEvents.add(appEvent);
    } else {
        getApplicationEventMulticaster().multicastEvent(appEvent, type);
    }
    // propagate to parent if present
    if (this.parent != null) {
        if (this.parent instanceof AbstractApplicationContext) {
            ((AbstractApplicationContext) this.parent).publishEvent(event, type);
        } else {
            this.parent.publishEvent(event);
        }
    }
}

During context refresh, initApplicationEventMulticaster creates the multicaster bean. If a bean named applicationEventMulticaster exists, it is used; otherwise a SimpleApplicationEventMulticaster is instantiated.

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

All ApplicationListener beans and listener beans are then registered with the multicaster:

protected void registerListeners() {
    for (ApplicationListener<?> listener : getApplicationListeners()) {
        getApplicationEventMulticaster().addApplicationListener(listener);
    }
    String[] listenerNames = getBeanNamesForType(ApplicationListener.class, true, false);
    for (String name : listenerNames) {
        getApplicationEventMulticaster().addApplicationListenerBean(name);
    }
    // dispatch early events
    Set<ApplicationEvent> early = this.earlyApplicationEvents;
    this.earlyApplicationEvents = null;
    if (early != null) {
        for (ApplicationEvent e : early) {
            getApplicationEventMulticaster().multicastEvent(e);
        }
    }
}

Multicaster Dispatch Logic

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

If an Executor is configured, listeners are invoked asynchronously.

Asynchronous Event Handling Options

Define a custom ApplicationEventMulticaster bean that sets a thread‑pool executor.

@Configuration
public class AsyncConfig {
    @Bean
    public SimpleApplicationEventMulticaster applicationEventMulticaster() {
        SimpleApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster();
        multicaster.setTaskExecutor(Executors.newFixedThreadPool(4));
        return multicaster;
    }
}

Manually submit work to an Executor inside a listener implementation.

Annotate an @EventListener method with @Async (requires @EnableAsync on a configuration class).

Annotation‑Based Listener Processing

During context initialization, AnnotationConfigUtils registers EventListenerMethodProcessor and DefaultEventListenerFactory. The processor scans all beans, finds methods annotated with @EventListener, and creates an ApplicationListener adapter via the appropriate EventListenerFactory. The adapter handles SpEL conditions, payload extraction, and optional result publishing.

// Register processor if missing
if (!registry.containsBeanDefinition(EVENT_LISTENER_PROCESSOR_BEAN_NAME)) {
    RootBeanDefinition def = new RootBeanDefinition(EventListenerMethodProcessor.class);
    beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_PROCESSOR_BEAN_NAME));
}
// Process a bean's @EventListener methods
Map<Method, EventListener> methods = MethodIntrospector.selectMethods(
        targetType, (MethodIntrospector.MetadataLookup<EventListener>) method ->
                AnnotatedElementUtils.findMergedAnnotation(method, EventListener.class));
for (Method m : methods.keySet()) {
    for (EventListenerFactory f : this.eventListenerFactories) {
        if (f.supportsMethod(m)) {
            ApplicationListener<?> al = f.createApplicationListener(beanName, targetType, m);
            context.addApplicationListener(al);
            break;
        }
    }
}

Summary

Spring’s event mechanism consists of a publisher ( ApplicationEventPublisher) and listeners ( ApplicationListener or @EventListener). The framework initializes an ApplicationEventMulticaster during context refresh, registers all listeners, and then dispatches events either synchronously or asynchronously based on the multicaster’s configuration. Custom events can be defined by extending ApplicationEvent, and built‑in framework events provide convenient extension points (e.g., reacting to ContextRefreshedEvent). Asynchronous handling can be achieved by supplying a thread‑pool multicaster, using an explicit Executor, or annotating listener methods with @Async. This design enables loose coupling, modularity, and flexible execution semantics within Spring applications.

backendJavaSpringAsynchronousEventApplicationContextlistener
Ximalaya Technology Team
Written by

Ximalaya Technology Team

Official account of Ximalaya's technology team, sharing distilled technical experience and insights to 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.