Spring Framework
  1. Spring Framework
  2. SPR-7637

ExceptinHandler method behaves differently than request handler

    Details

    • Type: Improvement Improvement
    • Status: Closed
    • Priority: Major Major
    • Resolution: Complete
    • Affects Version/s: 3.0.4
    • Fix Version/s: 3.1 M2
    • Component/s: Web
    • Labels:
      None
    • Last commented by a User:
      false

      Description

      I am new to Spring Framework. I am using @ExceptionHandler to handle exceptions in my controller. I thought the process of returning result from exception handler would be similar to a request handler but that is not the case. Below is my code.

      @RequestMapping(value = "/

      {userid}

      ", method = RequestMethod.GET)
      public @ResponseBody
      User getUser(@PathVariable(value = "userid") int userId) throws SpringTestException {
      logger.trace("Getting user: " + userId);
      User user = getUserDAO().getUser(userId);
      if (user == null)

      { throw new SpringTestException(1000, "User does not exists"); }

      logger.trace("Userid: " + user.getId() + ", Name: " + user.getName() + ", Email: "
      + user.getEmailId());
      return user;
      }

      This code fetches user from the database and returns it to the user in json. But when the user is not found in db I through an exception which is handled by an exception class and supposed to return error message in json format ("code": 10000, "message": "user does not exists"}). Below is my exception handler version 1.

      @ExceptionHandler(SpringTestException.class)
      public @ResponseBody
      HttpResponse handleSpringTestException(SpringTestException e)

      { logger.trace("Exception caught"); return new HttpResponse(e.getCode(), e.getMessage()); }

      HttpResponse has two members "code" and "message".

      When I make a curl call, I didn't get the response, instead got an exception with http-status 500.

      < HTTP/1.1 500 Internal Server Error
      < Server: Apache-Coyote/1.1
      < Content-Type: text/html;charset=utf-8
      < Content-Length: 2960
      < Date: Sun, 10 Oct 2010 12:35:33 GMT
      < Connection: close
      <
      <html><head><title>Apache Tomcat/6.0.20 - Error report</title><style><!--H1

      {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:22px;}

      H2

      {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:16px;}

      H3

      {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:14px;}

      BODY

      {font-family:Tahoma,Arial,sans-serif;color:black;background-color:white;}

      B

      {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;}

      P

      {font-family:Tahoma,Arial,sans-serif;background:white;color:black;font-size:12px;}

      A

      {color : black;}

      A.name

      {color : black;}

      HR

      {color : #525D76;}

      --></style> </head><body><h1>HTTP Status 500 - </h1><HR size="1" noshade="noshade"><p><b>type</b> Exception report</p><p><b>message</b> <u></u></p><p><b>description</b> <u>The server encountered an internal error () that prevented it from fulfilling this request.</u></p><p><b>exception</b> <pre>org.springframework.web.util.NestedServletException: Request processing failed; nested exception is com.ivasoft.springtest.common.SpringTestException: User does not exists
      org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:656)
      org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:549)
      javax.servlet.http.HttpServlet.service(HttpServlet.java:617)
      javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
      </pre></p><p><b>root cause</b> <pre>com.ivasoft.springtest.common.SpringTestException: User does not exists
      com.ivasoft.springtest.controllers.UserControllerIoC.getUser(UserControllerIoC.java:43)
      sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
      sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
      sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
      java.lang.reflect.Method.invoke(Method.java:616)
      org.springframework.web.bind.annotation.support.HandlerMethodInvoker.invokeHandlerMethod(HandlerMethodInvoker.java:176)
      org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.invokeHandlerMethod(AnnotationMethodHandlerAdapter.java:427)
      org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.handle(AnnotationMethodHandlerAdapter.java:415)
      org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:788)
      org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:717)
      org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:644)
      org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:549)
      javax.servlet.http.HttpServlet.service(HttpServlet.java:617)
      javax.servlet.http.HttpServlet.service(HttpServlet.java:717)

      • Closing connection #0
        </pre></p><p><b>note</b> <u>The full stack trace of the root cause is available in the Apache Tomcat/6.0.20 logs.</u></p><HR size="1" noshade="noshade"><h3>Apache Tomcat/6.0.20</h3></body></html>

      I googled around for hours and found that ExceptionHandlers are handled by different Adaptor than that of request handlers so I need to configure something in my xml file. I did this in my xml config file.

      <bean id="stringHttpMessageConverter"
      class="org.springframework.http.converter.StringHttpMessageConverter" />

      <bean id="jsonHttpMessageConverter"
      class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter" />

      <bean id="methodHandlerExceptionResolver"
      class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver">
      <property name="messageConverters">
      <list>
      <ref bean="stringHttpMessageConverter" />
      <ref bean="jsonHttpMessageConverter" />
      </list>
      </property>
      </bean>

      I got this response after configurations change (This is what I expected):

      < HTTP/1.1 200 OK
      < Server: Apache-Coyote/1.1
      < Content-Type: application/json
      < Transfer-Encoding: chunked
      < Date: Sun, 10 Oct 2010 12:37:43 GMT
      <

      • Connection #0 to host localhost left intact
      • Closing connection #0 {"message":"User does not exists","code":1000}

      I don't know if the above configuration is correct (This is working but could be a hack found by some developer) as I found it in some forum.

      Second level problem came when I used ResposeEntity as return value for my exception handler. I changed the exception handler to this

      @ExceptionHandler(SpringTestException.class)
      public @ResponseBody
      ResponseEntity<HttpResponse> handleSpringTestException(SpringTestException e)

      { return new ResponseEntity<HttpResponse>(new HttpResponse(e.getCode(), e.getMessage()), HttpStatus.NOT_FOUND); }

      And I was exception the same response with status code 404, but got this response.

      < HTTP/1.1 200 OK
      < Server: Apache-Coyote/1.1
      < Content-Type: application/json
      < Transfer-Encoding: chunked
      < Date: Sun, 10 Oct 2010 12:39:13 GMT
      <

      • Connection #0 to host localhost left intact
      • Closing connection #0
        {"statusCode":"NOT_FOUND","headers":{},"body":
        Unknown macro: {"message"}

        }

      Looks like the exception handlers behave differently that request handlers. I fail to understand why is it that way. I used following code to get required results.

      @ExceptionHandler(SpringTestException.class)
      public @ResponseBody
      HttpResponse handleSpringTestException(SpringTestException e, HttpServletResponse response)

      { logger.trace("Exception caught"); response.setStatus(HttpServletResponse.SC_NOT_FOUND); return new HttpResponse(e.getCode(), e.getMessage()); }

      This makes my code dependent on HttpServletResponse object and this is not the same as what I would do in a request handler. I could use @ResponseBody ResponseEntity as return value to get the same result and that seems correct to me. If exception handlers and request handlers behave the same way it would be much easier and consistent for the framework to work with.

        Activity

        Hide
        Rajeev Sharma added a comment -

        Last response text is mangled. Below is the correct one.

        < HTTP/1.1 200 OK
        < Server: Apache-Coyote/1.1
        < Content-Type: application/json
        < Transfer-Encoding: chunked
        < Date: Sun, 10 Oct 2010 12:39:13 GMT
        <
        < * Connection #0 to host localhost left intact
        < * Closing connection #0
        < {"statusCode":"NOT_FOUND","headers":{},"body": {"message":"User does not exists","code":1000}}

        Show
        Rajeev Sharma added a comment - Last response text is mangled. Below is the correct one. < HTTP/1.1 200 OK < Server: Apache-Coyote/1.1 < Content-Type: application/json < Transfer-Encoding: chunked < Date: Sun, 10 Oct 2010 12:39:13 GMT < < * Connection #0 to host localhost left intact < * Closing connection #0 < {"statusCode":"NOT_FOUND","headers":{},"body": {"message":"User does not exists","code":1000}}
        Hide
        Rossen Stoyanchev added a comment -

        I'm resolving this issue as complete. You will need to use ExceptionHandlerExceptionResolver, RequestMappingHandlerMapping and RequestMappingHandlerAdapter – new in Spring 3.1 M2 (see SPR-8214).

        The configuration is pretty much the same. Consider using the MVC namespace or Java-based configuration via @EnableWebMvc. Both of these will configure the JSON message converter as you would expect as long as it is available on the classpath.

        Show
        Rossen Stoyanchev added a comment - I'm resolving this issue as complete. You will need to use ExceptionHandlerExceptionResolver , RequestMappingHandlerMapping and RequestMappingHandlerAdapter – new in Spring 3.1 M2 (see SPR-8214 ). The configuration is pretty much the same. Consider using the MVC namespace or Java-based configuration via @EnableWebMvc. Both of these will configure the JSON message converter as you would expect as long as it is available on the classpath.
        Hide
        Adrian added a comment -

        I'm having several problems with the current solution. Also the solution doesn't not resolve the differences between a request handler and an exception handler. They still behave differently.

        Two examples I just ran into:

        All the models that are defined in the controller using @ModelAttribute annotated methods get lost after the execution of an exception handler.

        Any interceptors which are defined for the original request are not called after the execution of a exception handler.
        In my current project I have an interceptor that prefixes the viewname with a folder name depending of the device accessing the page (for example mobile/ or desktop/).

        I tried to look at the code in RequestMappingHandlerAdapter and ExceptionHandlerExceptionResolver and I see lots of code duplication. The constructors are basically the same. And the slight differences in the configuration of the argumentResolvers and the returnValueHandlers probably result in the differences that I described above.

        Please have a look at it again. I would really like to see request handlers and exception handler behave the same with the single difference that I can pass an exception as an argument into a exception handler method.

        Show
        Adrian added a comment - I'm having several problems with the current solution. Also the solution doesn't not resolve the differences between a request handler and an exception handler. They still behave differently. Two examples I just ran into: All the models that are defined in the controller using @ModelAttribute annotated methods get lost after the execution of an exception handler. Any interceptors which are defined for the original request are not called after the execution of a exception handler. In my current project I have an interceptor that prefixes the viewname with a folder name depending of the device accessing the page (for example mobile/ or desktop/). I tried to look at the code in RequestMappingHandlerAdapter and ExceptionHandlerExceptionResolver and I see lots of code duplication. The constructors are basically the same. And the slight differences in the configuration of the argumentResolvers and the returnValueHandlers probably result in the differences that I described above. Please have a look at it again. I would really like to see request handlers and exception handler behave the same with the single difference that I can pass an exception as an argument into a exception handler method.
        Hide
        Rossen Stoyanchev added a comment -

        Adrian, when an Exception arises in a controller it is caught by the DispatcherServlet and passed to a chain of HandlerExceptionResolvers. If you check the HandlerExceptionResolver contract, you'll see all that is available for resolving an exception and you'll know why an @ExceptionHandler method supports only a subset of the method arguments. Supported return value types on the other hand are aligned between @ExceptionHandler and @RequestMapping methods.

        Also keep in mind that even if we were to make the model content available, its actual contents would be unreliable since an exception could come from an @InitBinder or an @ModelAttribute method, or even from HandlerInterceptor.preHandle(..).

        Hope this explains it.

        Show
        Rossen Stoyanchev added a comment - Adrian, when an Exception arises in a controller it is caught by the DispatcherServlet and passed to a chain of HandlerExceptionResolvers. If you check the HandlerExceptionResolver contract, you'll see all that is available for resolving an exception and you'll know why an @ExceptionHandler method supports only a subset of the method arguments. Supported return value types on the other hand are aligned between @ExceptionHandler and @RequestMapping methods. Also keep in mind that even if we were to make the model content available, its actual contents would be unreliable since an exception could come from an @InitBinder or an @ModelAttribute method, or even from HandlerInterceptor.preHandle(..). Hope this explains it.
        Hide
        Rossen Stoyanchev added a comment -

        Marking this as resolved since Spring 3.1 M2 addressed the specific concerns in the original description for @ExceptionHandler methods to support a ResponseEntity return value.

        Show
        Rossen Stoyanchev added a comment - Marking this as resolved since Spring 3.1 M2 addressed the specific concerns in the original description for @ExceptionHandler methods to support a ResponseEntity return value.

          People

          • Assignee:
            Rossen Stoyanchev
            Reporter:
            Rajeev Sharma
            Last updater:
            Trevor Marshall
          • Votes:
            2 Vote for this issue
            Watchers:
            2 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved:
              Days since last comment:
              2 years, 19 weeks, 1 day ago