Reputation: 4114
I am handling my controlled exceptions using the following code:
@ExceptionHandler(MyException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ModelAndView handleMyException(MyException e) {
ModelAndView mav = new ModelAndView(ERROR_PAGE);
(...)
return mav;
}
That is, I want to both use custom views for different errors AND use response status code for the HTTP response.
At the same time, for pure 404 I have the following config in web.xml
<error-page>
<error-code>404</error-code>
<location>/404</location>
</error-page>
<error-page>
<error-code>400</error-code>
<location>/400</location>
</error-page>
Which takes to a 404 specific view.
The problem is that when a NOT_FOUND
is thrown from my @ExceptionHandled
method, it is not showing my custom view, debugging shows that execution actually goes through the handleMyException
method, but after it's done it also goes through the method that maps the /404
in web.xml, and that is the view that gets shown.
Also if I throw a different Response Code, I get the default behavior on Exceptions, instead of my custom view.
Upvotes: 3
Views: 3832
Reputation: 4637
I can't reproduce your problem with Tomcat 6 ans Spring 2.3.4. That is correct, because accroding to Servlet specification 2.5, the deployment descriptor defines a list of error page descriptions. The syntax allows the configuration of resources to be returned by the container either when a servlet or filter calls sendError on the response for specific status codes (...)
I tracked where Spring sets response code basing on @ResponseStatus(HttpStatus.NOT_FOUND) It is here:
public class ServletInvocableHandlerMethod (...)
private void setResponseStatus(ServletWebRequest webRequest) throws IOException {
if (this.responseStatus == null) {
return;
}
if (StringUtils.hasText(this.responseReason)) {
webRequest.getResponse().sendError(this.responseStatus.value(), this.responseReason);
}
else {
webRequest.getResponse().setStatus(this.responseStatus.value());
}
// to be picked up by the RedirectView
webRequest.getRequest().setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, this.responseStatus);
}
In my case if error handler method is annotated
@ResponseStatus(HttpStatus.NOT_FOUND)
the following branch is selected:
else {
webRequest.getResponse().setStatus(this.responseStatus.value());
}
Because HttpServletResponse.setStatus is called and NOT HttpServletResponse.sendError, web container ignores error page defined in <error-code>404</error-code>
I hope my explanation will be useful to track the problem yourself. I suspect somewhere HttpServletResponse.sendError is called and it triggers web container to return default error page
Upvotes: 3
Reputation: 4195
It sounds like the problem is probably that the web container is trying to handle the 400/404's its seeing from the web application (because it doesn't understand the context of those errors). You probably need to get rid of the web.xml error page definitions and add more configuration to the Spring controllers to handle the generic 400/404 errors as well.
This guide helped me a lot when I was setting up exception handling in my app: http://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc
The web.xml tells the app container how to handle various response codes that are generated by the application. When you get an exception out of a controller method, it gets handled by the Spring @ExceptionHandler annotated method. At this point, the app container isn't involved so it has no idea what's going on yet.
My best understanding is that when you generate a 404 Http status from the exception handler method and return, Spring's basically done at that point, and the app container steps back in and says "I got a 404, what do I do with a 404? ah, redirect to /404". And then, control goes back to the web app itself to handle the /404 request.
Upvotes: 1