This guide shows you how to implement custom error handling in Spring Boot. We use not only the well-known ControllerAdvice and ExceptionHandler annotations but also DefaultErrorAttributes and ErrorController to make your custom error attributes uniform and consistent.
Custom Error Handling in REST Controllers with Spring Boot
Table of Contents
First, this guide covers the basic topics: how to capture an exception at controller level in Spring Boot and map it to a given HTTP status code. We’ll do that with the
Then, we’ll see why we shouldn’t stop there and go one step further to have advanced, production-ready error handling in Spring Boot. In the sample code, we’ll show how to achieve this by customizing the error responses with our own Error Attributes and overriding the default
The sample application
I’ll use as a base for this post part of the Spring Boot app I created for the Guide to Testing Controllers in Spring Boot: SuperHeroes. It has a 2-layer composition with a Controller and a Repository that retrieves
SuperHero entities from a hardcoded map in code. More than enough for what we want to demonstrate.
To be able to “switch on and off” the different configurations, I included three properties in the Spring Boot’s
controller. We’ll see how to use them later in this post.
As you see in code, the method to get a superhero by id may throw a custom exception. The
NonExistingHeroException extends in our case from
RuntimeException, what makes it an unchecked exception.
Why do we need common error handling at all?
In Java, exceptions will just happen. If you’re building the common 3-tier application, they might be coming from your repository layer, business logic and/or controllers. You can keep the most relevant ones to you under control if they are somehow expected during your flow and you can recover from them. Many other exceptions will be just propagated to the controller level and it’s up to you to decide what to do with them.
Handling exceptions in code
If you decide to go the low-level way, you can find yourself surrounding all the methods that implement endpoints with try-catch blocks and deciding which error response and message should be returned by inspecting at the error. Maybe you can even extract some common logic, but still, you’ll have a lot of boilerplate try-catch blocks.
The Spring Web module comes with some tools that you can use to avoid all that unnecessary code. Let’s review in the coming sections the basics and also some advanced tips to make sure you build something you can be confident to deliver to production.
Why not letting exceptions to be managed by the server?
In case you don’t do anything with exceptions, they’ll be managed by the framework or the web server you use, normally following some default behavior to convert them to HTTP responses. These responses are just ugly, may expose your internal logic and your API consumers won’t know what to do after they get them. Not a good idea.
In practice: Spring Boot app with default error handling
To follow this guide, you can either clone the repository from Github or build your own from scratch. Just bear in mind that I won’t include all the code in this post to keep it focused on the error handling, although I’ll link the most relevant classes.
If you cloned the repository, make sure all configuration properties are set to
Then our application context won’t include any customizations or specific configuration. It’s just an app with a controller and a repo that throws an exception. Now, you can use your preferred IDE to run the app or, from the command line:
Let’s make some HTTP calls and see what happens. I use HTTPie since it’s available for multiple platforms, and I’m also trimming part of the responses that are not relevant for the explanation. You can also use other tools like
curl or the UI-powered Postman.
Case 1: Get a Superhero
Call the GET endpoint with a proper ID and you’ll get a superhero in JSON format.
Case 2: Force the exception
If the ID is bigger than the size of the list, the application throws a
NonExistingHeroException. Without a proper
ExceptionHandler, this ends up with the stacktrace in the application logs and a 500 Internal Server Error status. Not nice at all, since it gives the impression that something failed on the server’s side but actually this should be treated as a Bad Request (let’s assume we told our clients in docs that only indexes 1-4 are available).
Pay also attention to the format of the error. It’s a JSON structure with a few generic fields. This is actually coming from Spring Boot, which injects a default error controller
BasicErrorController [javadoc] (injected by default in the web context) that uses a set of
DefaultErrorAttributes [javadoc] (injected as a bean in the context). Spoiler: we’ll override both these beans later in this guide, to perform a fully customized error handling with Spring Boot.
Case 3: Try getting a superhero in HTML
Rest Controllers in Spring Boot will return
application/json by default but, what if we try to get a superhero asking for HTML content?
This is weird. We get something called Whitelabel Error Page, in HTML, even though we’re trying to build a web service that only spits JSON. It seems, at least, that the HTTP status code is consistent: 406 Not Acceptable.
The problem with this response is that, even though you could say it’s the client’s fault (asked for HTML), the error format is not consistent with the previous error format (JSON). Not a big deal but, if somebody is testing this service from a web browser, we can do better and don’t provide HTML at all if we are implementing a REST API that is supposed to deal only with JSON content.
Case 4: Try getting an invalid id using HTML
Let’s do the same again but now with an invalid id:
This is even weirder. If we don’t accept HTML, why would we let an internal error to be propagated? Now they get a 500 status code and this error because the code finds the exception first (before Spring tries to convert the response).
Case 5: The classic 404
Let’s ask for an invalid resource and see what happens. First, accepting JSON (HTTPie requests default to JSON):
Now, asking for HTML response:
Again, it’s at least debatable whether we should respond with a Not Found 404 page or just tell the client we don’t accept HTML (406), given that our intention is to build a REST API using JSON.
Conclusion: why do you need proper error handling in Spring Boot?
If you want your service to look robust and production-ready, it’s better than you use some of the available tools in Spring Boot to customize your error handling behavior instead of using the basic defaults.
In this post, we’ll focus on some core requirements:
- Manage exceptions and translate them to proper HTTP Status Codes.
- Avoid returning HTML responses in Spring Boot if we are only accepting JSON.
- Unify Error formats so their structure always looks the same.
Basics: ControllerAdvice and ExceptionHandler in Spring
A Controller Advice is just a kind of interceptor that surrounds the logic in your Controllers and allows you to apply some common logic to them. If you know Aspect-Oriented Programming, the word Advice will be familiar to you anyways.
You can simply annotate a class with
@ControllerAdvice to make it the default one for all your controllers. If you prefer to have more than one, the annotation also allows you to specify to which packages, classes or types it applies.
Since Spring 4.3, there is a new
@RestControllerAdvice annotation that is quite handy when you plan to write exception handlers. It’s a combination of
@ResponseBody automatically added to all your methods annotated with
@ResponseBody annotation indicates that you’re sending a response body as a result of handling an error, which is what you typically want to do in a REST API if you want to keep your API consumers happy.
The Controller Advice may be used for a few different tasks but the most popular one is to capture exceptions from your application stack and translating them to HTTP responses in an organized manner. To achieve this, you use the
@ExceptionHandler annotation in a method and indicate which type of Exception you want to handle. The request and the exception instance will be injected via method arguments if you specify them.
You can use this annotation together with
@ResponseBody as indicated before, if you want to return contents in the response body. We won’t use it in the example since we use
@RestControllerAdvice and therefore those come implicitly.
Also, you may want to use
@ResponseStatus to set the HTTP status code that you want to return for that exception. However, given that we’re planning to create our own
ResponseEntity with custom contents, we’ll include the response status when constructing the response.
In practice: Controller Advice and Exception Handler
The first step to improve our Spring Boot application to deal with exceptions is adding an Exception Handler for, at least, our custom exception.
We can start with something like this:
If you’re running the repo example, you have to set
superheroes.errors.controlleradvice=true and modify the final version of the code to look like the version above. Note that we’re adding the
@ConditionalOnProperty annotation to be able to switch on and off capabilities for the sake of this tutorial. You can omit that part if you want.
Now, let’s check again the Case 2: Force the exception and see what the response is.
Cool? Now the response status code is 404 Not Found, that’s exactly what we wanted. We also got rid of the errors in the logs (stacktrace) since this exception is now captured. However, what about the response body? It seems that we have overridden the default behavior and, since our method doesn’t return a response, this is translated to a response without body. Not cool, because the case 5 (asking for a non existing resouce e.g.
/supers/3) still returns a body using Spring Boot’s
DefaultErrorAttributes. So, we have a new inconsistency.
We can fix that, by adding a
ResponseEntity return result to our exception handler method. You may think that it’s even possible to instantiate a
DefaultErrorAttributes object and just mimic what Spring Boot is doing by default. But that is not the intention of that class, you can’t simply create a new instance and pass the attribute values you want.
A workaround would be creating a bean that has exactly the same fields as the
DefaultErrorAttributes class. Or you could simply return a Map from the exception handler. However, this feels a bit hacky because you would be adapting your logic to the default one embedded in Spring Boot. You would also need to know part of the internals to return consistent responses.
So, how do we proceed? Simple: we can just create our own
ErrorAttributes bean and inject it in the context. We’ll be then overriding the
DefaultErrorAttributes with our custom implementation and we can use that error structure for all our errors, instead of the default one.
Creating a Custom Error Schema for Exception Handlers
Custom Error Example
The idea is simple, we want to use our own error format and replace these default fields:
For this example, we will try to convert all our API errors in Spring Boot using the Google JSON Style Guide. By doing that, I’m not recommending you to do this in your projects; it’s just an example. You may have your own error style guide (simpler than this) or use any other convention available on the Internet.
In the Google JSON Style Guide, this is the format of the API errors:
The specification is not very strict so you may have your own interpretation for fields like
Custom error attributes using Google JSON style guide with Spring Boot
First, we model this error structure in Java. Note that, for the sake of the example, I simplified the logic in this class so it only contains one error in the array of errors proposed by the Google specs. I also added the field from specs
sendReport [link] because it’s a useful example of how our API clients could report incidents using a unique identifier that we can correlate in our backend service.
The chosen field names and class structure follows the resulting JSON we want to achieve so we can let the JSON serializer do its job without including more conversion logic. Note that there is a handy method
fromAttributeMap() to make the conversion from the default fields used by Spring Boot. You could also build them from scratch if you don’t want to depend on them, for that you could have a look at some of the logic included in
Returning the custom response from the Exception Handler
We have already defined our error format so we can start using it in the no-response-content ExceptionHandler we implemented in the first place. To do that, we just create a new error object and return it with a
With our new code, we run again the Case 2: Force the exception and see what the response is.
Looks better. What we got now is not only a 404 Not Found response but also an error message that follows the Google JSON style guide. In real life, we’d implement the web page that supports the report sending functionality.
We just started making errors homogeneous. Now, we only get this error if that particular exception is thrown since it’s in an exception handler. We want to avoid creating exception handlers for each exception in Java so let’s see how we can override the defaults in Spring Boot to customize the REST API error format returned.
Customize REST error responses in Spring Boot
Overriding DefaultErrorAttributes returned by controllers
Once we have defined our error structure, we have to replace the default configuration.
First, we create a class that extends
DefaultErrorAttributes. As indicated before, you can also go from scratch and create your own implementation implementing the interface
What we’re doing here is a kind of wrapper that overrides the default implementation of
getErrorAttributes and replaces the default values by our own ones, using the mapper method we created in
SuperHeroAppError. Note that the method signature in the interface
ErrorAttributes requires us to return a Map; that’s why we created that method
SuperHeroAppError instead of serializing the complete object in JSON.
Now, we can add a Configuration class to override the default bean.
Again, we use here
@ConditionalOnProperty just to be able to turn it on and off; you don’t need it in your code. As you see, the only thing we need is to inject an
ErrorAttributes bean in the context to make Spring Boot not to inject the default implementation. The API version and base report URI come from the
In practice: Example of overriding ErrorAttributes in Spring Boot
Now, your configuration should look as follows:
After running the application, we can test what happens when any generic error occurs, for example the Case 5: a classic 404 Not Found.
What happens now is that our own implementation of
ErrorAttributes is being injected in the context and used to build the error response. This logic lives in a
BasicErrorController implementation [link] that Spring Boot injects by default in the web context when you use the web dependencies. It’s configured by default with the route
/error, which is itself the one used by the web server to render the error when something wrong occurs.
The reason why I’m explaining the
BasicErrorController logic is that it’s the part we’re going to customize next. Even though we already have a good base implementation to handle all kind of errors, it’s still possible to get a Whitelabel error page if you try the cases 4 or 5 described above (passing a header
Customize ErrorController to avoid HTML Whitelabel error pages
Basic error controller in Spring Boot
As introduced before, Spring Boot loads a default
BasicErrorController [link] into the web application context when we use the web dependencies. This basic controller handles errors for both JSON and HTML response types. It’s also, as its name suggests, a basic implementation of
AbstractErrorController, and it’s in this parent class where we find the
ErrorAttributes field and logic that we’ve been taking advantage of.
Given that we only want to get rid of the HTML error response handling but we want to keep the
ErrorAttributes, I’ll create for this application a new
SuperHeroErrorController that extends
AbstractErrorController. There is also the option to implement the interface
ErrorController [link] from scratch.
We’ll use an even more simplified version of the Basic Controller:
@ConditionalOnProperty annotation is there just to be able to switch this on and off. You don’t need it.
When we enable this controller, Spring Boot will detect that there is already one in the web context and won’t use the basic implementation. Our error controller doesn’t use any view resolvers (we pass an empty list) since we’re not using views from MVC, just building a REST API. We simply take any error and map it to our customized response, given that this controller will get our
SuperHeroErrorAttributes via constructor injection.
If you cloned the repository, it’s time to set all properties as enabled:
Then, run the application and you can try some requests for HTML. You’ll get an empty-content response this time, with the corresponding HTTP status code 406 Not Acceptable.
HttpMediaTypeNotAcceptableException also gets handled by our custom implementation of the controller and the response attributes are set, but we didn’t configure any HTML conversion for a
SuperHeroAppError so the response body is empty. This is descriptive enough to flag any distracted HTML API clients with the status code, but you could also consider returning something else in response if you need it, via configuring a dedicated
@ExceptionHandler for that exception.
Add custom error codes to REST error responses
To finish this guide, let’s cover a minor change in the sample code that can make your error responses even better for your API clients: the introduction of specific error codes.
In our example, we already have the ability to generate a unique identifier for each error that we could use to correlate support requests with internal logs (we don’t have any, but you can get the idea).
However, what we can also do is to generate custom application error codes to cover the most common error types. For instance, we could have a code like
NE001 that goes together with our 404 responses but it’s more specific: it will be returned only if clients try to get IDs from the
superheroes resource that don’t exist. This helps API clients because some HTTP status codes are too generic to determine exactly what went wrong, and you can use application codes instead of the description messages -which may vary- in your documentation.
First, we create an interface that any class may implement to provide a generic Error Code. We’ll use it for Exceptions.
Next step is to implement it in the
Now, we can use the error code in our exception handlers in the controller advice. The style guide we chose is not very strict about the
code field so we’re not doing anything crazy here. Remember that we’re using the standard 404 code as the HTTP response status anyway.
If you now try to access a non-existing id, you’ll get this error in the customized response. As mentioned before, these application-specific error codes are sometimes very useful to give more information to your API clients, and they’re easier to find in docs compared to language-specific error messages.
In this guide, you learned how to create custom error responses in Spring Boot’s REST Controllers. We covered not only the classic
@ExceptionHandler examples but also more advanced topics like changing error fields in the response using
ErrorAttributes, and overriding the default
BasicErrorController to be more in control about the content we show. In this case, we used this knowledge to avoid displaying the white label error page.
Having a consistent error format and proper error description is a big advantage when developing REST APIs. It helps your API consumers understand what exactly went wrong and improve user experience by displaying descriptive errors.
If you’re building a Microservices Architecture, this is normally one of the concerns that you want to extract as a common artifact. You could build your own small library with these classes and use it in your Microservices. You’ll get a consistent error handling mechanism even though you may have multiple Spring Boot applications.
I hope you find this guide useful. As always, if you want to learn more about other topics you can have a look at:
- The Practical Architecture Process mini-book, with advice about how to create and maintain proper Software Architecture in an effective way.
- A Full Reactive Guide to learn how to build a full-stack reactive web application.
And don’t forget to comment and give a star to the GitHub repository!