Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ExceptinHandler method behaves differently than request handler [SPR-7637] #12293

Closed
spring-issuemaster opened this issue Oct 10, 2010 · 5 comments

Comments

@spring-issuemaster
Copy link
Collaborator

commented Oct 10, 2010

Rajeev Sharma opened SPR-7637 and commented

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":{"message":"User does not exists","code":1000}}

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.


Affects: 3.0.4

2 votes, 2 watchers

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Oct 10, 2010

Rajeev Sharma commented

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}}

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Jun 3, 2011

Rossen Stoyanchev commented

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

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.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Sep 5, 2011

Adrian commented

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.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Dec 13, 2011

Rossen Stoyanchev commented

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.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Dec 13, 2011

Rossen Stoyanchev commented

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
2 participants
You can’t perform that action at this time.