Exception handling is a very essential feature of any Java application. Every good open-source framework, such as Spring Boot, allows writing the exception handlers in such a way that we can separate them from our application code. Well, Spring framework also allows us to do so using the annotations @ControllerAdvice and @ExceptionHandler.
- The
@ControllerAdvice
annotation is used to define a class that will handle exceptions globally across all controllers. Its methods are annotated with@ExceptionHandler
,@InitBinder
, and@ModelAttribute
annotations. - The
@ExceptionHandler
annotation is used to handle specific exceptions. The annotated method is invoked when the specified exceptions are thrown from a @Controller. We can define these methods either in a @Controller class or in @ControllerAdvice class.
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ApplicationException.class)
public ResponseEntity<String> handleApplicationException(ApplicationException ex) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ex.getMessage());
}
}
Let us learn about these annotations in more detail.
1. “Local” vs. “Global” Exception Handling
A local exception handler is created in a @Controller (or @RestControllerAdvice) class, it will handle the exceptions thrown from the @ExceptionHandler annotated handler methods within the controller class only.
@Controller
public class PageController {
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public ModelAndView getPage(@PathVariable("id") String id) throws Exception {
Page page = ...;
if(page == null) {
throw new RecordNotException("Page not found for id : " + id);
}
return new ModelAndView("index");
}
@ExceptionHandler(NullPointerException.class)
public ModelAndView handleNullPointerException(NullPointerException ex) {
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("error");
modelAndView.addObject("message", ex.getMessage());
return modelAndView;
}
@ExceptionHandler(RecordNotException.class)
public ModelAndView handleException(RecordNotException ex) {
...
return modelAndView;
}
}
A global exception handler is defined using the @ControllerAdvice (or @RestControllerAdvice) annotation on a class and @ExceptionHandler annotation on methods. The methods will be invoked when the specified exception is thrown from any of the @Controller (or @RestController) handler methods.
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(NullPointerException.class)
public ModelAndView handleNullPointerException(NullPointerException ex) {
//...
}
@ExceptionHandler(RecordNotException.class)
public ModelAndView handleException(RecordNotException ex) {
//...
}
}
Now, every time any controller encounters a NullPointerException in the request processing, the control will automatically move to the handleNullPointerException() method.
2. @ExceptionHandler Annotation
When we annotate a method with the @ExceptionHandler annotation, Spring configuration detects this annotation and registers the method as an exception handler for the argument exception class and its subclasses.
2.1. Method Arguments
The @ExceptionHandler annotated methods allow for very flexible signatures. They can accept arguments of different types. These additional method arguments allow to access relevant context information related to the exception and the request thus generating more informative error responses or performing additional actions.
For example:
- Specified exception type
- HttpServletRequest and HttpServletResponse
- WebRequest
- Principal and Authentication
- HttpSession
- Locale
- Model
@ExceptionHandler(NullPointerException.class)
public ModelAndView handleException(NullPointerException ex, HttpServletRequest request, Model model) {
...
}
2.2. Return Types
Similar to arguments, return types can be different. The return type determines how the error response will be generated and returned to the client.
The commonly used return types are:
- ResponseEntity
- ModelAndView
- String (View Name)
- ResponseBody
- Void (writing the response content directly to
HttpServletResponse
)
In the following example, ErrorResponse is a new feature and exposes HTTP error response details, including HTTP status, response headers, and a body of type ProblemDetail.
@ExceptionHandler(AppException.class)
public ModelAndView handleAppException(AppException ex) {
ModelAndView modelAndView = new ModelAndView("errorPage");
modelAndView.addObject("errorMessage", ex.getMessage());
return modelAndView;
}
@ExceptionHandler(DataNotFoundException.class)
@ResponseBody
public ErrorResponse handleDataNotFoundException(DataNotFoundException ex) {
return new ErrorResponse(ex.getMessage());
}
We may combine the ExceptionHandler
annotation with @ResponseStatus
for a specific HTTP error status.
@ExceptionHandler(AppException.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public String handleAppException(AppException ex) {
return "errorPage";
}
2.3. Handling Multiple Exceptions
As mentioned earlier, the above exception handler will handle all exceptions, either instances of a given class or sub-classes of argument exceptions. But, if we want to configure @ExceptionHandler for multiple exceptions of different types, we can specify all such exceptions in the form of an array.
@ExceptionHandler({NullPointerException.class, ArrayIndexOutOfBoundsException.class, IOException.class})
public ModelAndView handleException(Exception ex) {
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("generic-error");
modelAndView.addObject("message", ex.getMessage());
return modelAndView;
}
3. @ControllerAdvice Annotation
If we want to centralize the exception-handling logic to one class that is capable of handling exceptions thrown from any handler class/controller class – then we can use @ControllerAdvice
annotation.
By default, the methods in an @ControllerAdvice apply globally to all controllers. We can create a class and add @ControllerAdvice annotation on top. Then add @ExceptionHandler methods for each type of specific exception class in it.
@ControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler
{
@ExceptionHandler(Exception.class)
public final ResponseEntity<ErrorResponse> handleAllExceptions(Exception ex, WebRequest request) {
List<String> details = new ArrayList<>();
details.add(ex.getLocalizedMessage());
ErrorResponse error = new ErrorResponse(ApplicationConstants.SERVER_ERROR, details);
return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
@ExceptionHandler(RecordNotFoundException.class)
public final ResponseEntity<ErrorResponse> handleUserNotFoundException(RecordNotFoundException ex, WebRequest request) {
List<String> details = new ArrayList<>();
details.add(ex.getLocalizedMessage());
ErrorResponse error = new ErrorResponse(ApplicationConstants.RECORD_NOT_FOUND, details);
return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
}
}
4. ResponseEntityExceptionHandler Class
In the above example, we extended the exception handler class with ResponseEntityExceptionHandler. It is a convenient base class for @ControllerAdvice classes designed specifically for handling exceptions in a RESTful API context where we need to return different types of responses based on the exception type.
ResponseEntityExceptionHandler provides exception handlers for internal Spring exceptions. If we don’t extend it, then all the exceptions will be redirected to DefaultHandlerExceptionResolver
which returns a ModelAndView
object which is typically used in a traditional MVC setup for rendering views.
However, when building RESTful APIs, returning ModelAndView
objects might not be suitable, as APIs typically require structured data formats like JSON or XML for error responses. That’s where ResponseEntityExceptionHandler
comes in.
5. Demo
For the demo, the below handler method intentionally returns NullPointerException.
@RequestMapping(value="/demo/not-exist", method = RequestMethod.GET, headers="Accept=*/*")
public @ResponseBody ModelAndView oneFaultyMethod()
{
if(true)
{
throw new NullPointerException("This error message if for demo only.");
}
return null;
}
If we deploy the above application and hit the URL [/SpringApplication/users/demo/not-exist] in the browser, it will show the “error” page as configured in the first section.
< %@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
< %@ taglib prefix="x" uri="http://java.sun.com/jstl/xml" %>
< %@ taglib prefix="fmt" uri="http://java.sun.com/jstl/fmt" %>
< %@ taglib prefix="sql" uri="http://java.sun.com/jstl/sql" %>
<html>
<head>
<title>This is sample error page</title>
</head>
<body>
<h1>This is sample error page : <c:out value="${message}"></c:out></h1>
</body>
<html>
The output in the browser will be below.

Happy Learning !!
Comments