Mastering Events in Spring Boot: A Comprehensive Guide

Hiten Pratap Singh
hprog99
Published in
6 min readMay 30, 2023

Spring Boot Events are a part of the Spring Framework’s context module. These events are meant to provide a means of application-wide broadcasting of information, allowing beans to interact without being tightly coupled.

Consider a common scenario where a new user registers on a website. Multiple actions should take place: sending a welcome email, logging the registration event, updating the user count, etc. Rather than having the user registration service directly call these services, we can publish an event that these services listen for. This method maintains a level of separation and modularity.

Basic Spring Boot Events

In Spring Boot, events are published using the ApplicationEventPublisher interface. Spring’s ApplicationContext interface extends this, meaning any Spring-managed bean can publish events.

To create a custom event, you can extend ApplicationEvent:

public class UserRegistrationEvent extends ApplicationEvent {
private String username;

public UserRegistrationEvent(Object source, String username) {
super(source);
this.username = username;
}

public String getUsername() {
return username;
}
}

This UserRegistrationEvent is fired whenever a new user registers. It carries the username as its payload.

To publish this event, we’ll use the ApplicationEventPublisher:

@Service
public class UserRegistrationService {
@Autowired
private ApplicationEventPublisher eventPublisher;

public void registerUser(String username) {
// ... registration logic ...

eventPublisher.publishEvent(new UserRegistrationEvent(this, username));
}
}

To listen for this event, we create a listener by implementing ApplicationListener:

@Component
public class UserRegistrationListener implements ApplicationListener<UserRegistrationEvent> {
@Override
public void onApplicationEvent(UserRegistrationEvent event) {
// ... handle event ...
}
}

Whenever a UserRegistrationEvent is published, the onApplicationEvent method of our listener is called.

Using Annotations

Spring also provides a more concise way of handling events using annotations. With the @EventListener annotation, any method in a managed bean can act as an event listener.

@Component
public class UserRegistrationListener {
@EventListener
public void handleUserRegistrationEvent(UserRegistrationEvent event) {
// ... handle event ...
}
}

This allows for more flexible event handling, as different methods can handle different events, or a single method can handle multiple events.

Asynchronous Events

By default, event listeners are synchronous in Spring Boot. The publishEvent method blocks until all listeners have finished processing the event. For lengthy operations, this may not be ideal. Spring Boot provides an easy way to make event listeners asynchronous.

First, enable asynchronous event listeners in your application configuration:

@Configuration
@EnableAsync
public class SpringAsyncConfig {}

Then, simply add the @Async annotation to your listener:

@Component
public class UserRegistrationListener {
@Async
@EventListener
public void handleUserRegistrationEvent(UserRegistrationEvent event) {
// ... handle event ...
}
}

Now, the handleUserRegistrationEvent method will be executed in a separate thread, and publishEvent will return immediately.

Conditional Events in Spring Boot

Spring Boot also provides the capability to conditionally handle events based on certain criteria. This allows us to finely control when our listener reacts to an event.

You can make use of the @EventListener's condition attribute to define a SpEL expression that evaluates whether or not the event should be handled:

@Component
public class UserRegistrationListener {
@EventListener(condition = "#event.username.startsWith('admin')")
public void handleAdminRegistrationEvent(UserRegistrationEvent event) {
// ... handle event ...
}
}

In the code above, the listener method will only handle the UserRegistrationEvent if the username starts with ‘admin’.

Transactional Events

Spring Boot allows for events to be tied into the application’s transaction management system, leading to ‘transactional events’. Transactional events are only published after the successful completion of a transaction.

To publish a transactional event, you need to use the TransactionalApplicationEventPublisher:

@Service
public class UserRegistrationService {
@Autowired
private TransactionalApplicationEventPublisher eventPublisher;

@Transactional
public void registerUser(String username) {
// ... registration logic ...

eventPublisher.publishEvent(new UserRegistrationEvent(this, username));
}
}

Transactional events are listened to in the same way as standard events, however, the listener will only be triggered after the successful commit of the transaction.

Order of Event Execution

By default, the order of execution for multiple listeners of the same event is undefined in Spring Boot. However, you may have scenarios where you need to control this order. To do so, implement the Ordered interface or use the @Order annotation:

@Component
@Order(1)
public class FirstUserRegistrationListener implements ApplicationListener<UserRegistrationEvent> {
@Override
public void onApplicationEvent(UserRegistrationEvent event) {
// ... handle event ...
}
}

@Component
@Order(2)
public class SecondUserRegistrationListener implements ApplicationListener<UserRegistrationEvent> {
@Override
public void onApplicationEvent(UserRegistrationEvent event) {
// ... handle event ...
}
}

In the above example, FirstUserRegistrationListener will always handle the event before SecondUserRegistrationListener.

Generic Events

Spring Boot 4.2 introduced a new type of event: generic events. With generic events, the event class can contain additional type information. To define a generic event, use the ResolvableTypeProvider interface:

public class GenericUserRegistrationEvent<T> extends ApplicationEvent implements ResolvableTypeProvider {
private T user;

public GenericUserRegistrationEvent(Object source, T user) {
super(source);
this.user = user;
}

public T getUser() {
return user;
}

@Override
public ResolvableType getResolvableType() {
return ResolvableType.forClassWithGenerics(getClass(), ResolvableType.forInstance(user));
}
}

You can listen for a generic event like any other event:

@Component
public class UserRegistrationListener {
@EventListener
public void handleGenericUserRegistrationEvent(GenericUserRegistrationEvent<User> event) {
// ... handle event ...
}
}

Error Handling in Event Listeners

One critical aspect to consider when working with events in Spring Boot is error handling. What happens when an error occurs during the processing of an event?

By default, an exception thrown during event handling stops the execution and propagates to the publisher. However, Spring Boot provides mechanisms to handle these exceptions.

Using @EventListener's mode attribute

You can use the mode attribute of the @EventListener annotation to define the error handling mode for a particular listener:

@Component
public class UserRegistrationListener {
@EventListener(mode = EventListenerMode.ASYNC)
public void handleUserRegistrationEvent(UserRegistrationEvent event) {
// ... handle event ...
}
}

In the code snippet above, the listener is set to asynchronous mode. In this mode, if an exception occurs, it won’t stop the execution of other listeners nor propagate to the publisher.

Using a dedicated error handling listener

Spring Boot also allows for a dedicated error handling listener that can listen for any errors occurring during event handling:

@Component
public class UserRegistrationErrorListener {
@EventListener(condition = "#root.cause instanceof SomeException")
public void handleUserRegistrationError(DataAccessException exception) {
// ... handle error ...
}
}

In the example above, the handleUserRegistrationError method will be called whenever a DataAccessException is thrown during the handling of any event.

Testing Event Listeners

Testing is an integral part of application development, and event listeners are no exception. To test event listeners in Spring Boot, you can use the ApplicationEventPublisher:

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserRegistrationListenerTest {
@Autowired
private ApplicationEventPublisher eventPublisher;

@Autowired
private UserRegistrationListener userRegistrationListener;

@Test
public void testHandleUserRegistrationEvent() {
UserRegistrationEvent event = new UserRegistrationEvent(this, "testUser");

eventPublisher.publishEvent(event);

// ... verify listener behavior ...
}
}

In the above code snippet, the testHandleUserRegistrationEvent method publishes a UserRegistrationEvent, and then the behavior of the listener can be verified.

Real-World Use-Cases for Spring Boot Events

There are numerous scenarios in which using events in Spring Boot is advantageous. Here are a few examples:

Logging

Logging is an important aspect of any application. For instance, you might want to log every time a user registers. By creating a UserRegistrationEvent, you can centralize this logic in a dedicated listener.

Notifications

You might want to notify other parts of your application or external systems when certain events occur. For example, when a new user registers, you might want to send a welcome email. A listener can handle this.

Statistics

Gathering statistics about your application can be done by listeners. For example, you could count the number of user registration events to track the number of new users over time.

Best Practices

In addition to understanding how to use events in Spring Boot, it is also important to understand how to use them effectively. Here are some best practices:

Keep listener logic minimal

The logic within event listeners should be kept to a minimum. It should mainly serve as glue between different parts of your application. Any substantial logic should be placed within service classes that the listener can call.

Don’t rely on the order of events

While you can control the order of event execution to a certain extent with the Ordered interface or the @Order annotation, it’s a best practice to design your listeners to be order-independent whenever possible.

Use conditional and transactional events judiciously

While conditional and transactional events can be powerful tools, they should be used judiciously. Overuse can lead to hard-to-debug problems in your application.

Spring Boot events are a powerful, flexible tool that can enhance the modularity and decoupling of your application. Whether you’re logging, sending notifications, gathering statistics, or doing something entirely unique, events provide a robust solution.

By understanding the different types of events, knowing how to handle and test them, and following best practices, you can leverage the power of events in Spring Boot to the fullest.

--

--