Exception handling is an integral part of any application. It involves catching errors and showing a graceful error message to the end user.
Normally, try-catch
blocks are used in a java application to handle exceptions where the error raised can be shown on a console or UI.
But, in a web application, the error has to be sent to the browser in the form of a proper response.
This means that there should be a mechanism where any error raised from any part of the application is sent to the browser.
Spring Exception Handling Mechanism
Spring MVC provides an easy and centralized solution for exception handling in MVC applications.
Using Spring’s exception handling mechanism, developers can write the exception handling code at one single location so that exception raised from any part of the application will arrive at this central location.
There are a couple of ways to achieve this :
- Specify an exception handler method for a class whose methods can throw an exception.
Any uncaught exception thrown by the methods of this class will be directed to this exception handler.
The exception handler will only work for the methods of the class in which the handler method is written.
It is achieved by using@ExceptionHandler
annotation over the exception handler method. - Specify a global exception handler class containing exception handling methods so that any uncaught exception thrown from the entire application will at last arrive at one of the exception handler methods before the response is sent to the browser.
This is achieved using@ExceptionHandler
and@ControllerAdvice
annotations.
Using @ExceptionHandler
This annotation is applied over a method which acts as an exception handler.
This annotation takes an exception class as argument so that the method knows which types of exceptions it should handle.
Example,
@ExceptionHandler(IOException.class) public void exceptionHandler() { }
In the above piece of code, an exception handler method exceptionHandler
is declared which is annotated with @ExceptionHandler
with java.io.IOException
as value.
This means that the method exceptionHandler
will catch any uncaught exceptions of type java.io.IOException
from the class in which this method is declared.
Spring provides much flexibility in declaring exception handler methods annotated with @ExceptionHandler
annotation.
This method can :
- Have any return type
The return type can bevoid
, aString
,ModelAndView
object,java.lang.Map
or a custom JSON object depending on the requirement.Thus all the below signatures are valid.@ExceptionHandler(IOException.class) public void exceptionHandler(){ } @ExceptionHandler(IOException.class) public String exceptionHandler(){ return "error"; } @ExceptionHandler(IOException.class) public ModelAndView exceptionHandler(){ ModelAndView errorView = new ModelAndView("errorPage"); return errorView; } @ExceptionHandler(IOException.class) public Map<String, String> exceptionHandler(){ Map<String, String> errorMap = new HashMap<String, String>(); return errorMap; }
In case of exception handler method returning a
String
, the controller will return error.jsp(depending on the view resolver configured).
In case of return type asModelAndView
, errorPage.jsp will be returned and so on. - Flexible argument types
It can accept no arguments, argument representing the exception raised and theHttpServletRequest
object as shown below.
When the exception is provided as argument, it will contain the actual exception object which is thrown from the application.@ExceptionHandler(IOException.class) public void exceptionHandler(){ } @ExceptionHandler(IOException.class) public String exceptionHandler(IOException error){ return "error"; } @ExceptionHandler(IOException.class) public ModelAndView exceptionHandler(HttpServletRequest request, IOException error){ ModelAndView errorView = new ModelAndView("errorPage"); errorView.addObject("errorObj", error); errorView.addObject("url", request.getRequestURL()); return errorView; } @ExceptionHandler(IOException.class) public Map<String, String> exceptionHandler(IOException error){ Map<String, String> errorMap = new HashMap<String, String>(); errorMap.put("errorMesg", error.getMessage()); return errorMap; }
If you want that the exception object is available on the jsp(or the view), return a
ModelAndView
object and add the error object to thisModelAndView
as shown in the third example.
In this case, the exception object will be available on the view bound by the name “errorObj”.
Example
Below is an example of an exception handler written in a controller class.
This example uses a custom exception which is created as follows.
package com.codippa; public class AppException extends Exception { private String error; public AppException (String message) { super(message); error = message; } public String getError() { return error; } }
Controller class is written as
package com.codippa; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.ModelAndView; @Controller public class TestController { /** * Method to serve the requested URL * @return */ @RequestMapping("/home") public ModelAndView showHomePage() { ModelAndView homePage = new ModelAndView("home"); String s = "throw"; //voluntarily throw exception if(s.equals("throw")){ throw new AppException ("Exception example"); } return homePage; } /** * Exception handler method for AppException * * @return * @throws */ @ExceptionHandler(AppException.class) public ModalAndView exceptionHandler(AppException e){ ModelAndView homePage = new ModelAndView(); homePage.setViewName("error"); homePage.addObject("error", e); return homePage; } }
In the above example, when the controller method throws AppException
, it is caught by the method exceptionHandler
written in the same class since it is annotated with @ExceptionHandler
configured to handle exceptions of type AppException
.
Since the return type of the method is a ModelAndView
with view set to “error”, the response will be error.jsp.
Exception object is set in the ModelAndView
object with the name “error”, hence it can be accessed in the jsp using this name.
Contents of error.jsp could be written as
<html>
<body>
Error : ${error.message}
</body>
</html>
where message
is a field in java.lang.Exception
.
Global Exception Handler : @ControllerAdvice
The above approach is suitable when the exception handler method is present in the same class from which the exception is thrown.
In scenarios where you want a centralized exception handling mechanism so that exceptions thrown across the application are handled at one place, there is another method.
This method uses @ControllerAdvice
annotation along with @ExceptionHandler
.
In this method, a separate class containing exception handler is created.
This class is annotated with @ControllerAdvice
and contains exception handler methods which are annotated with @ExceptionHandler
annotation.
A single class can contain multiple exception handler methods for handling different types of exceptions.
Example
package com.codippa; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.servlet.ModelAndView; @ControllerAdvice public class GlobalErrorHandler { /** * Method called when IOException is thrown from the application * * @param e * @return */ @ExceptionHandler(IOException.class) public ModelAndView inputOutputError(Exception e) { ModelAndView error = new ModelAndView("error"); error.addObject("errorObj", e); error.addObject("errorMesg", "IO Error"); return error; } /** * Method called when AppException is thrown from the application * * @param e * @return */ @ExceptionHandler(AppException.class) public ModelAndView applicationError(AppException e) { ModelAndView error = new ModelAndView("error"); error.addObject("errorObj", e); error.addMessage("errorMesg", "Application Error"); return error; } }
In the above example, a GlobalErrorHandler class is declared.
This class is annotated with @ControllerAdvice
annotation and it contains multiple methods annotated with @ExceptionHandler
with different exception types which act as error handlers.
This class acts as a helper to all the application controllers.
Thus, whenever any uncaught exception is thrown from any controller, it arrives to the appropriate error handler methods in this class.
Both the methods of this class return error.jsp which displays the error message sent from both the methods.
Contents of error.jsp are
<body>
Error : ${errorMesg}
</body>
</html>
Let’s tweak in
- A class annotated with
@ControllerAdvice
acts as a supporter to application controllers and catches only those uncaught exceptions which are thrown from controller classes. - Polymorphism does not work in case of exception types handled by the exception handlers.
That is, suppose a controller method throws an exception and there is no matching exception handler method for this exception but there is an exception handler for its super class, then this exception will NOT be caught by the exception handler.
Example,
let’s say a controller method throws ajava.io.FileNotFoundException
and there is an exception handler method forjava.io.IOException
, then this exception will not be caught by this handler. - When returning a String from a controller method, you need to apply
@ResponseBody
annotation before the method otherwise, Spring will search for a view matching the name of this String.
Hope the article was useful.