Spring MVC part V: Exception handling

Today we’ll dig into the exception handling mechanism of Spring MVC 3.1

All the code mentioned in this article is based on Spring MVC 3.1. If you are using previous version of Spring MVC some assertions may not hold.

Please note that all the examples in this post can be found in a demo application on GitHub https://github.com/doanduyhai/SpringMVCExceptionGHandling

 

I Exception handling infrastructure

A Main classes

To manage exceptions, Spring MVC 3.1 offers by default the following classes:

  • ExceptionHandlerExceptionResolver: generic exception handler
  • DefaultHandlerExceptionResolver: exception handler supporting a set of predefined exceptions
  • SimpleMappingExceptionResolver: good old exception handler (available since 2003!) to map a custom exception to a specific error page

It is possible to use those 3 handlers, all we need to do is to declare a HandlerExceptionResolverComposite which is basically a container of Exception handlers and delegates the exception handling to each of the registered handler.

	<bean id="compositeExceptionResolver" class="org.springframework.web.servlet.handler.HandlerExceptionResolverComposite">
		<property name="exceptionResolvers">
			<list>
  				<bean class="exceptionHandler1"/>
  				<bean class="exceptionHandler2"/>
  				<bean class="..."/>
  			</list>
		</property>
		<property name="order" value="0"/>
	</bean>

With this configuration, each exception handler wil be invoked with respect to their declaration order (a list is ordered by nature)

 

B Detailed description

1) ExceptionHandlerExceptionResolver

This exception handler is the default handler and covers 80% of the use cases. On startup the class is registered in the Spring context with a set of default method argument resolvers and method return values handlers:

Default argument resolvers

  • ServletRequestMethodArgumentResolver: supports the following types as method arguments
    • WebRequest
    • ServletRequest
    • MultipartRequest
    • HttpSession
    • Principal (for security)
    • Locale
    • InputStream
    • Reader
  • ServletResponseMethodArgumentResolver: supports the following types as method arguments
    • ServletResponse
    • OutputStream
    • Writer

In addition to these method arguments, this handler also handle the following return types and method annotations:

  • Return types:
    • ModelAndView
    • Model
    • View
    • String: simple view name return
    • Map
    • HttpEntity: to customize Http request body as well as header
  • Annotations:
    • @ModelAttribute
    • @RequestBody
    • @ResponseBody

 

2) DefaultHandlerExceptionResolver

This class simply handles the following default exceptions:

 

Exception Http Code
NoSuchRequestHandlingMethodException 404 (Not Found)
HttpRequestMethodNotSupportedException 405 (Method Not Allowed)
HttpMediaTypeNotSupportedException 415 (Unsupported Media Type)
MissingServletRequestParameterException 400 (Bad Request)
ServletRequestBindingException 400 (Bad Request)
ConversionNotSupportedException 500 (Internal Server Error)
TypeMismatchException 400 (Bad Request)
HttpMessageNotReadableException 400 (Bad Request)
HttpMessageNotWritableException 500 (Internal Server Error)
MethodArgumentNotValidException 400 (Bad Request)
MissingServletRequestPartException 400 (Bad Request)

 

You need not declare any method with @ExceptionHandler for the above exceptions, they will be handled automatically by the class.

 

3) SimpleMappingExceptionResolver

 
This class lets you map an exception to an view. Simply define its “exceptionMappings” property by providing a list of exception/view pair values:

<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
	<property name="exceptionMappings">
		<props>
			<prop key="java.lang.ClassNotFoundException">pages/classNotFoundException</prop>
			<prop key="java.lang.CloneNotSupportedException">pages/cloneNotSupportedException</prop>
		</props>
	</property>
</bean>

Above, we map the ClassNotFoundException to the view “pages/classNotFoundException” and CloneNotSupportedException to “pages/cloneNotSupportedException“. Of course you must ensure that the view declaration is correct and the actual pages exist.

 

II Exception handling strategies

In this chapter we discuss about various strategies to handle exceptions.

A Return an error message to be displayed

This is the simplest use case for exception handling:

@RequestMapping(value = "rest/exception1")
public String exception1()
{
	throw new NullPointerException("Exception1 as plain text with <strong>html</strong> tags");
}

@ExceptionHandler(NullPointerException.class)
@ResponseBody
public String handleException1(NullPointerException ex)
{
	return ex.getMessage();
}

Above, on NullPointerException detection, we return the source message. Since the handler method is annotated with @ResponseBody, the source message will be simply put in the response body.

On the client side, some Javascript to display an error panel with the message:

function exception1()
{
	$.ajax({
		type: 'GET',
		url: "rest/exception1",
        success: function(data)
        {
        	$('#messagePanel').empty().html(data).show();
        }
    });
	
	return false;
}

Please notice that the returned exception message is handled by a success function (line 6) in Javascript. Indeed even if an Exception occured at the server side, as long as the Http response status is 200 the client side will consider it as a success.

springmvcexceptionhandling-exception1
 

B Return a dedicated error page

This strategy simply returns a generic error page and hides all the exception details from the end user.

@RequestMapping(value = "http/exception2", method = RequestMethod.GET)
public String exception2()
{
	throw new IndexOutOfBoundsException();
}

@ExceptionHandler(IndexOutOfBoundsException.class)
public String handleException2(IndexOutOfBoundsException ex)
{
	return "pages/errorPage";
}

Please note that there is no redirect, we simply render a generic error page instead of the normal target page.


 

C Return an error code with custom message

In this strategy, we let Spring build an generic error page based on the Http code we provide with a custom message.

@RequestMapping(value = "http/exception3", method = RequestMethod.GET)
public String exception3()
{
	throw new IllegalStateException("Exception3 with response status");
}
@ExceptionHandler(IllegalStateException.class)
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR, reason = "exception3")
public void handleException3(IllegalStateException ex, HttpServletResponse response) throws IOException
{

}

Please note the definition of Http code and custom error message with the @ResponseStatus annotation at line 7. This strategy is quite equivalent to defining the error page at servlet level in the web.xml:

 <error-page>
	<error-code>404</error-code>
	<location>/pages/404.html</location>
  </error-page>

The result:
springmvcexceptionhandling-exception3

 

D Redirect to a custom page with custom error message

In this strategy, we do a real HTTP redirect to a custom error page with a custom error message.

@RequestMapping(value = "http/exception4", method = RequestMethod.GET)
public String exception4() throws FunctionalException
{
	throw new FunctionalException("Functional exception");
}

@ExceptionHandler(FunctionalException.class)
public RedirectView handleException4(FunctionalException ex, HttpServletRequest request) throws IOException
{
	RedirectView redirectView = new RedirectView("../errorRedirectPage");
	redirectView.addStaticAttribute("errorMessage", ex.getMessage());
	return redirectView;
}

@RequestMapping(value = "errorRedirectPage")
public String errorRedirectPage(HttpServletRequest request, Model model, @RequestParam("errorMessage") String errorMessage)
{
	model.addAttribute("errorMessage", errorMessage);
	return "pages/errorRedirectPage";
}

The above code has some hacks. First in the exception handler we put the error message as static attribute of the redirect view object (line 11). It will end up being appended in the query string:

errorRedirectPage?errorMessage=xxxx

Then in the error message method handler, we extract it from the query with @RequestParam(“errorMessage”) (line 16)and put it back into the model object.

Why such a hack ? Why don’t we use the RedirectAttribute map that exists in Spring MVC 3.1 ? Simply because the ExceptionHandlerExceptionResolver class does not support this method argument.

Of course we could have added the RedirectAttributesMethodArgumentResolver as custom argument resolver but it would require a binderFactory (RedirectAttributesMethodArgumentResolver:53) and the current infrastructure of ExceptionHandlerExceptionResolver does not support.


 

E Return an exception wrapper object in JSON format, AJAX response

In this strategy, we return an exception wrapper object serialized with JSON format back to the client

@RequestMapping(value = "rest/exception5", method = RequestMethod.GET)
public String exception5(HttpSession session)
{
	throw new IllegalArgumentException("Test exception 5 with ExceptionVO as JSON data");
}

@ExceptionHandler(IllegalArgumentException.class)
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
@ResponseBody
public ExceptionVO handleException5(IllegalArgumentException ex, HttpServletResponse response) throws IOException
{
	ExceptionVO exceptionVO = new ExceptionVO("handleException5()", "HomeController", ex.getMessage());

	return exceptionVO;

}

To achieve this, we need to add a @ResponseBody annotation to the exception handler method but also a @ResponseStatus annotation to send an Http error code.

Unlike the strategy described at A, in this case we want to trigger the error listener at client-side so we need to send back an Http status other than 200.

function exception5()
{
	$.ajax({
		type: 'GET',
		url: "rest/exception5",
		dataType: 'application/json; charset=UTF-8',
		error: function(jqXHR, textStatus, errorThrown) 
		{
			var exceptionVO = jQuery.parseJSON(jqXHR.responseText);
		   
			$('#errorModal')
			.find('.modal-header h3').html(jqXHR.status+' error').end()
			.find('.modal-body p>strong').html(exceptionVO.clazz).end()
			.find('.modal-body p>em').html(exceptionVO.method).end()
			.find('.modal-body p>span').html(exceptionVO.message).end()
			.modal('show');
		   
		}
    });
	
	return false;
}

In the error handler function, we need to extract the exception wrapper object from the body of the returned page (jqXHR.responseText, line 9) and convert it to an JSON object.

Next we can extract various exception information from the wrapper object and display them in a modal panel. The way to display the error details is up to you (modal panel, custom message box …)

springmvcexceptionhandling-exception5
 

III Demo application

The demo application can be found on GitHub at https://github.com/doanduyhai/SpringMVCExceptionGHandling

The main demo page displays a list of buttons to trigger an exception. There is one button for each exception handling strategy described above.

I also added some test cases for the DefaultHandlerExceptionResolver and SimpleMappingExceptionResolver handlers.

springmvcexceptionhandling-demo

 
 
 

About these ads

About DuyHai DOAN
Java freelancer LinkedIn profile : http://fr.linkedin.com/pub/duyhai-doan/2/224/848 Follow me on Twitter: @doanduyhai

19 Responses to Spring MVC part V: Exception handling

  1. Ashwin Raj says:

    I currently use an @ExceptionHandler annotated error handling method with a String return value. The method returns a view name which is resolved to a JSP (view) page. Recently, I added a method to this @Controller for handing Ajax requests. When the new method throws an exception, the same handler method is unable to provide any feedback to the browser. What may be further complicating this problem is the fact that the exception is originating from an implementation of HandlerInterceptorAdapter, and not from the Controller method or down that function call stack.

    • DuyHai DOAN says:

      Hello Ashwin

      I’ve done a quick test. Even if the exception is thrown in your request interceptor, if you have defined a proper exception handling method at controller level (with the correct Exception class), the exception should be caught.

      Looking at the source code of DispatcherServlet.doDispatch() method (DispatcherServlet.doDispatch() ) we can see that all the interceptor handling is wrapped by generic exception catcher (line 942).

      Please ensure that you define a generic Exception class for your exception handler and especially put the exception handler in the correct @Controller.

      If your interceptor is triggered when calling, let’s say, the /example/test URL, then you should put the exception handler in the @Controller handling this URL.

  2. venu says:

    Hi DuyHai,

    I am doing spring mvc+jpa application,i have requirement that reset password option,when the user clicks reset password i am showing current pass,new pass,confirm pass fields ,so that user can reset his password ,if the user enter current password correct every thing works fine,but if user not entered the current password correct it throws 500 exception stack trace that current password is not matching in console,i have to catch that exception ,and i should show that message in the same page ,please help me to solve this exception handling.

    Thanks in advance

    • DuyHai DOAN says:

      Hello Venu

      You should look at the piece of code that throws the HTTP 500 error. As far as I know the “reset” password feature is not provided out-of-the box by Spring Security so it is done somewhere in your application

  3. Prashant says:

    Excellent Stuff, Thanks Much!!!!!!!!!

  4. Vidya says:

    Wt is the difference between (object==null) and (null==object). ?
    Which one is better or preferred ?

  5. Pingback: fstyle.de » Spring MVC: Exception Handling

  6. Todd P. says:

    This tutorial saved me tons of time, was exactly what I needed.

    I’m using Spring MVC 3.2 to build a rich webapp, so there are many ajax requests from the client-side. Your strategy E “Return an exception wrapper object in JSON format, AJAX response” is what I needed.

    Thank you so much for taking the time to share your knowledge!

  7. Rachid says:

    thanks for your great work keep going

  8. Dsr says:

    Nice tutorial…
    I’ve a doubt in throwing exception from DAO layer. If I throw an exception like “throw MyException(“Duplicate entry”)”, I need to show this message in the same screen where user performs submission. Means I do not need to show error message in new screen, but in the same screen at the top. How can achieve this…Pls give some idea to accomplish this…

  9. Pingback: Valider ses POJOs en REST avec Bean validation, Spring MVC et JQuery | Frédéric Camblor Dev Blog

  10. k says:

    Any examples on handling exceptions thrown from the filter layer and not application, especially popular in the spring security stack

  11. liberate.org says:

    It doesn’t matter if you’re trying to save your relationship after cheating, stop a divorce, or you just want
    to get your ex back. Unfortunately Nora Ephron recently passed
    away, but she left us with some of our most endearing romantic comedies
    of all time — from “When Harry Met Sally” to “Sleepless in Seattle”
    and “You’ve got Mail. Well, it can easily be because the audience has been watching her in the TV for the past 6 years, but nevertheless, her FACTS are easy to remember and apply to everyone.

  12. krishna says:

    how to get model and view when clicking on back button by using interceptors in spring mvc?

    • krishna says:

      From search page->results page ->detail results page. Each stage there is back button. It should begeneric and useful for all pages.No hardcoded passing values any where.Any suggestion would be appreciate.

  13. Since these products are acknowledged to are
    able to increase Human Growth Hormone within the body, they’re
    able to also assist with weight loss. Some individuals will go so far as claim these are same product with assorted
    brand names. My search continuing to get a suitable treatment for my quick aging.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 39 other followers

%d bloggers like this: