I am trying to set up a large-scale REST services server. We're using Spring Boot 1.2.1 Spring 4.1.5, and Java 8. Our controllers are implementing @RestController and the standard @RequestMapping annotations.
My problem is that Spring Boot sets up a default redirect for controller exceptions to /error
. From the docs:
Spring Boot provides an /error mapping by default that handles all errors in a sensible way, and it is registered as a ‘global’ error page in the servlet container.
Coming from years writing REST applications with Node.js, this is, to me, anything but sensible. Any exception a service endpoint generates should return in the response. I can't understand why you'd send a redirect to what is most likely an Angular or JQuery SPA consumer which is only looking for an answer and can't or won't take any action on a redirect.
What I want to do is set up a global error handler that can take any exception - either purposefully thrown from a request mapping method or auto-generated by Spring (404 if no handler method is found for the request path signature), and return a standard formatted error response (400, 500, 503, 404) to the client without any MVC redirects. Specifically, we are going to take the error, log it to NoSQL with a UUID, then return to the client the right HTTP error code with the UUID of the log entry in the JSON body.
The docs have been vague on how to do this. It seems to me that you have to either create your own ErrorController implementation or use ControllerAdvice in some fashion, but all the examples I've seen still include forwarding the response to some kind of error mapping, which doesn't help. Other examples suggest that you'd have to list every Exception type you want to handle instead of just listing "Throwable" and getting everything.
Can anyone tell me what I missed, or point me in the right direction on how to do this without suggesting up the chain that Node.js would be easier to deal with?
Although this is an older question, I would like to share my thoughts on this. I hope, that it will be helpful to some of you.
I am currently building a REST API which makes use of Spring Boot 1.5.2.RELEASE with Spring Framework 4.3.7.RELEASE. I use the Java Config approach (as opposed to XML configuration). Also, my project uses a global exception handling mechanism using the
@RestControllerAdvice
annotation (see later below).My project has the same requirements as yours: I want my REST API to return a
HTTP 404 Not Found
with an accompanying JSON payload in the HTTP response to the API client when it tries to send a request to an URL which does not exist. In my case, the JSON payload looks like this (which clearly differs from the Spring Boot default, btw.):I finally made it work. Here are the main tasks you need to do in brief:
NoHandlerFoundException
is thrown if API clients call URLS for which no handler method exists (see Step 1 below).ApiError
) which contains all the data that should be returned to the API client (see step 2).NoHandlerFoundException
and returns a proper error message to the API client (see step 3).Ok, now on to the details:
Step 1: Configure application.properties
I had to add the following two configuration settings to the project's
application.properties
file:This makes sure, the
NoHandlerFoundException
is thrown in cases where a client tries to access an URL for which no controller method exists which would be able to handle the request.Step 2: Create a Class for API Errors
I made a class similar to the one suggested in this article on Eugen Paraschiv's blog. This class represents an API error. This information is sent to the client in the HTTP response body in case of an error.
Step 3: Create / Configure a Global Exception Handler
I use the following class to handle exceptions (for simplicity, I have removed import statements, logging code and some other, non-relevant pieces of code):
Step 4: Write a test
I want to make sure, the API always returns the correct error messages to the calling client, even in the case of failure. Thus, I wrote a test like this:
The
@ActiveProfiles("dev")
annotation can be left away. I use it only as I work with different profiles. TheRegexMatcher
is a custom Hamcrest matcher I use to better handle timestamp fields. Here's the code (I found it here):Some further notes from my side:
@EnableWebMvc
annotation. This was not necessary in my case.By default Spring Boot gives json with error details.
It also works for all kind of request mapping errors. Check this article http://www.jayway.com/2014/10/19/spring-boot-error-responses/
If you want to create log it to NoSQL. You can create @ControllerAdvice where you would log it and then re-throw the exception. There is example in documentation https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc
Solution with
dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);
and@EnableWebMvc @ControllerAdvice
worked for me with Spring Boot 1.3.1, while was not working on 1.2.7I think
ResponseEntityExceptionHandler
meets your requirements. A sample piece of code for HTTP 400:You can check this post
New answer (2016-04-20)
Using Spring Boot 1.3.1.RELEASE
New Step 1 - It is easy and less intrusive to add the following properties to the application.properties:
If working with a full RESTful Application, it is very important to disable the automatic mapping of static resources since if you are using Spring Boot's default configuration for handling static resources then the resource handler will be handling the request (it's ordered last and mapped to /** which means that it picks up any requests that haven't been handled by any other handler in the application) so the dispatcher servlet doesn't get a chance to throw an exception.
New Answer (2015-12-04)
Using Spring Boot 1.2.7.RELEASE
New Step 1 - I found a much less intrusive way of setting the "throExceptionIfNoHandlerFound" flag. Replace the DispatcherServlet replacement code below (Step 1) with this in your application initialization class:
In this case, we're setting the flag on the existing DispatcherServlet, which preserves any auto-configuration by the Spring Boot framework.
One more thing I've found - the @EnableWebMvc annotation is deadly to Spring Boot. Yes, that annotation enables things like being able to catch all the controller exceptions as described below, but it also kills a LOT of the helpful auto-configuration that Spring Boot would normally provide. Use that annotation with extreme caution when you use Spring Boot.
Original Answer:
After a lot more research and following up on the solutions posted here (thanks for the help!) and no small amount of runtime tracing into the Spring code, I finally found a configuration that will handle all Exceptions (not Errors, but read on) including 404s.
Step 1 - tell SpringBoot to stop using MVC for "handler not found" situations. We want Spring to throw an exception instead of returning to the client a view redirect to "/error". To do this, you need to have an entry in one of your configuration classes:
The downside of this is that it replaces the default dispatcher servlet. This hasn't been a problem for us yet, with no side effects or execution problems showing up. If you're going to do anything else with the dispatcher servlet for other reasons, this is the place to do them.
Step 2 - Now that spring boot will throw an exception when no handler is found, that exception can be handled with any others in a unified exception handler:
Keep in mind that I think the "@EnableWebMvc" annotation is significant here. It seems that none of this works without it. And that's it - your Spring boot app will now catch all exceptions, including 404s, in the above handler class and you may do with them as you please.
One last point - there doesn't seem to be a way to get this to catch thrown Errors. I have a wacky idea of using aspects to catch errors and turn them into Exceptions that the above code can then deal with, but I have not yet had time to actually try implementing that. Hope this helps someone.
Any comments/corrections/enhancements will be appreciated.
What about this code ? I use a fallback request mapping to catch 404 errors.