Spring Web And WebFlux Exception Handling Best Practices

Artem Ptushkin
CodeX
Published in
4 min readJun 15, 2022

Problem

Working on web applications it’s just a matter of time when you write your exception handling classes and there’re many ways to do it. Particularly we aim to control how our API clients see the errors, e.g. the exceptions thrown from the controller.

If you’re really a fast online surfer — jump to the example source code at GitHub. It’s well structured and includes tests to prove the concept.

Code design expectations

Let’s talk about what we really want to achieve here.

First of all, we should define day-to-day work and it’s adding another exception or trace a current exception behavior.

Secondly, we should agree on the contract with our clients and, probably, we want to return the same data structure with any exception occurring to keep everything concise. Moreover, we want to scale the solution to other services in our company/project.

  • The solution should be scalable and we have an easy way to add one more exception to manage
  • It should be easy to traceback were an exception comes from
  • We should find in a single file the list of known exceptions and their status code to be mapped
  • It should map all the exceptions occurring without any exclusion and the latter should be controlled in this single code place

Web

In general, there are 2 ways to handle exceptions coming from the web:

  • @ControllerAdvice + @ExceptionHandler
  • Annotation exception class with @ResponseStatus

The second one doesn’t provide an option to map a message custom and eventually you come to the first approach, more flexible.

The most widespread error designing exception handling in spring is missing the part that when filters throw an exception you won’t catch it ExceptionHandler as it never reaches DispatcherServlet . If you’re lucky then you get into this online, for instance, here is a StackOverflow comment that has been tagged 45 times that tells about this.

The reasons why we should care about filters are:

  • You might have a starter with a filter another day for request authentication
  • Any library from your company stack might include a filter that you don’t know and it will throw an exception another day that you won’t catch in ExceptionHandler

Hence, the long-term solution here is to extend OncePerRequestFilter as it’s written in the very same StackOverflow answer but you or your colleagues might not get there if they don’t make a proper question. The question usually comes when you face a problem and want to fix it properly.

Combining

The latter solution is to combine OncePerRequestFilter and ExceptionHandler in a loosely coupled way to be able to break the dependency easily another day. It should be some exception handling filter, like:

This class delegates to your exception handler annotated with ControllerAdvice thus all our exceptions come to a single place in your codebase!

What’s left is to think about a simple way to convert your exceptions to the domain response you need, for example:

{
"message": "I'm a demo exception from controller",
"code": 400
}

The most scalable data structure that doesn’t require you to write a boilerplate if code every time is Map and in Spring you can always define it as a bean:

Having you mapping like this your routine day-to-day work of handling a new exception is simple — just add another entry to the map!

Bean configuration

You can find an example bean configuration here. Please, pay attention to this update from sprig security and see Spring blog for more.

In Spring Security 5.7.0-M2 we deprecated the WebSecurityConfigurerAdapter, as we encourage users to move towards a component-based security configuration

WebFlux

There are many ways to handle exceptions coming from the web layer in WebFlux. But good news — there is no need to think about filter issues as we have in the web, you can define a common global exception handler and start from it in your service. All the exceptions will be handled, please find proof tests in my repository.

The WebFlux exception handler that relies the same way on the Map can look like this:

Default status code

The important moment to keep it loosely coupled is to externalize the default status code to respond in case of an unexpected exception. We have this approach as well in the web example up above.

Having code like this, it’s easy to:

  • test your exception handler with any other default response codes
  • if you move the class into a library you let your consumers change the default behavior

Summary

It’s possible to achieve loosely coupled code with Spring that will be convenient for:

  • testing
  • refactoring
  • decomposition

In general, try to focus on:

  • define interfaces
  • pass everything through the constructor (do not initialize object properties inside the constructor)
  • initialize everything as a bean
  • testing

You can find examples in my repository of how to test your exception handling functionality. Test scenarios include all the specific cases that are important to cover and described in the article. It includes web and webflux setups, and a neat way to switch in tests between these two.

--

--

Artem Ptushkin
CodeX
Writer for

Software engineer, clean code enthusiast, and contract testing expert