Spring events — make your code more flexible

events vs direct method calls

Jiří Čížek
5 min readJan 2, 2023
Photo by Brett Jordan on Unsplash

Spring events are a nice feature of Spring that can help reduce dependencies between application components. As a result, your code will be loosely coupled and hence more flexible, maintainable and testable.

Disclaimer — I’m not trying to say that you should only use events and never call the method directly ;)

It is an implementation of the publish-subscribe pattern. Publishers and listeners are isolated and have no dependency on each other. It allows us to easily add new functionality by adding new code (new listener for the event) without affecting the existing code, which is in line with the OCP (Open Close Principle).

Testing is also much more straightforward. No matter how many functionalities you want to call from one point of the code, you only test that particular event was emitted. The event processing is tested in other tests focused on specific actions the event was intended to trigger.

Boundaries of components that need to be mocked

Let’s give it a try

Three terms have to be clarified beforehand:

  1. Event — it is a POJO that holds all the data we want to transfer between components
  2. Publisher — the component responsible for emitting events. In Spring, it is the ApplicationEventPublisher bean
  3. Listener — processes the particular event

As we said, the event is a POJO so it can look like this:

Events are published via the Spring bean ApplicationEventPublisher via the method publishEvent. So all you need to do is to inject the publisher and use it.

Finally, the listener is a method annotated as @EventListener and receives a specific event. Spring will automatically register every event listener.

I’m using spring boot 2.7.3 for demonstration purposes, but I want to mention that there are limitations for spring version < 4.2.

  • An event has to extend `ApplicationEvent`.
  • A listener has to implement `ApplicationListener`.

Synchronous and asynchronous listeners

By default, Spring events are synchronous. When the event is published, the main thread blocks until all listeners have finished processing the event and then continues.

synchronous listeners

It is a nice behaviour, as it allows you to easily try spring events without dealing with the complexities of asynchronous processing. In other words, you can take your existing code and refactor it to use events instead of calling methods directly (when it makes sense), and the application should work as before.

But sometimes, you do not want the event listener to block the thread. E.g. order completion can trigger generating a PDF report for internal purposes, which takes some time. Because customers don’t care about your internal report, there is no need to make them wait for it to be created. Now, asynchronous event listeners come into play. You can make the event listener asynchronous by adding @Async annotation. To make it work, you must add @EnableAsync into your configuration class.

asynchronous listener

However, asynchronous event listeners have some limitations.

  1. No return value is allowed (the purpose of returning values from event listeners will be described at the end of the article in the section “advanced spring events features”)
  2. The exception thrown in the event listener won’t be propagated to the caller thread.

The first one is a fact, and there is nothing we can do about it. There is a way to handle the exceptions, though. All you need is to implement AsyncUncaughtExceptionHandler and override the default implementation provided by AsyncConfigurer.

Drawbacks of events

Of course, spring events are not a silver bullet and have cons too.
Like any other new shiny tool, there is a danger of overusing it. So use it where you can benefit from it. Because, and in my opinion, this is the main disadvantage of events, the code using events is a bit more complex and hence less readable, especially to someone not familiar with the event-based code. And that’s simply because you cannot read the business flow from one class. You must go through all the participating event listeners to see what happens there.

Advanced spring events features

To conclude this article, I want to mention Spring events’ advanced features briefly.

Transactional bound listeners

Imagine you have the following components — OrderService, ShipingService and EmailService. When you complete an order via OrderService, you publish an event. ShipingService processes the event to forward the order for shipment, and EmailService processes it to notify the customer that the order has been processed.
This action should be performed in the transaction, so if ShipingService fails, the order is discarded (not the best solution, it is for demonstration purposes only). Simplified, it could look like this.

transaction lifetime

The transaction is rolled back when shipment creation fails to preserve data consistency. But what about the email? It is already sent and cannot be rolled back. The transactional listener is one way to fix this. Spring can delay the processing of an event until the specific transaction outcome. Instead of annotating the method as @EventListener, annotate it as @TransactionalEventListener(phase = <phase>), where the phase is the particular transaction result at which the listener should be triggered.

You have four phases to choose from:

  • BEFORE_COMMIT
  • AFTER_COMMIT
  • AFTER_ROLLBACK
  • AFTER_COMPLETION
possible transaction outcome and particular triggered phase

So by changing the @EventListener annotation of our listener responsible for sending email notifications to @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT), the issue described above will be fixed.

Filtered listeners

Sometimes, you want to ignore the event being processed under certain circumstances. You can write an if statement in the event handler, but you can also use the conditional processing that listeners offer. Conditions can be defined in the field condition in the @EventListener annotation in SPEL. E.g. @EventListener(condition = “#orderCompletedEvent.isUpToDate”)

Return value from a listener

Spring listeners have an amazing or wild (depending on how you will use it) functionality. Usually, a method annotated as @EventListener returns void — because what else should it return, right? Well, event listeners can actually return value, and it’s not wasted. If the returned value is another event, meaning that there is a registered listener for the object. The event is automatically emitted and processed. And that’s not all, you can even return a collection of events, and all of them are automatically emitted.

Hope it was helpful ;)

--

--

Jiří Čížek

Software engineer/architect a DevOps enthusiast, dedicated to crafting quality architectures and maintainable clean, pragmatic code