3

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.

M Rajoy
  • 4,028
  • 14
  • 54
  • 111
  • Your question is unclear to me. 1) What value has ERROR_PAGE? 2) What is actual behavior, what is wanted behavior? 3)Please clarify that "NOT_FOUND is thrown from my @ExceptionHandled method" means "@ExceptionHandled method set reponse code to NOT_FOUND" – michaldo Dec 05 '13 at 09:28
  • what is it you don't understand? – M Rajoy Dec 05 '13 at 09:28
  • to sum up: I want to show my custom error view when a certain exception is thrown, and still return a response code of 404. The problem is, by defining the default action when an actual 404 happens, it overrides the view I set on my @ExceptionHandler method. – M Rajoy Dec 05 '13 at 09:30
  • Clear now. But answer is unknown... – michaldo Dec 05 '13 at 09:38
  • What is your Spring version and application server? I checked Spring 3.2.5.RELEASE and Jetty 9.0.6 and I can't reproduce your case. And I think it shouldn't be reproducible according to Servlet 3.0 specification (with given Spring version) – michaldo Dec 07 '13 at 22:18
  • But before start dig into Spring code, let's eliminates the most stupid reason. I'm sure you checked it, but: Are you 100% sure that new ModelAndView(ERROR_PAGE) matches your custom error page? – michaldo Dec 07 '13 at 22:47
  • I am using Spring 3.2.4.Release with Tomcat 6.0. Yes I am positive that it matches my custom error page, in fact if I remove the @ResponseStatus annotation altogether I do get that custom error page. – M Rajoy Dec 08 '13 at 08:18

2 Answers2

3

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

michaldo
  • 4,195
  • 1
  • 39
  • 65
  • Thanks for your thorough analysis. Now I think I can see where the problem is: I am actually annotating my methods with a reason(using `@ResponseStatus(value=HttpStatus.NOT_FOUND, reason="My custom reason")`, so judging by the code you posted, that makes Spring go through the alternate branch. Unfortunately, I do need to define my own status message, so this is kind of a bummer for me. I guess I'll have to look for a different approach then. – M Rajoy Dec 08 '13 at 15:19
  • @kelmer Why not include your status message (reason) as part of the custom view? [Also related](http://stackoverflow.com/questions/5637950/spring-mvc-using-responsestatusreason-on-a-responsebody-exception-hand). – Sithsu Dec 02 '14 at 18:36
1

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.

reblace
  • 4,115
  • 16
  • 16