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
@ControllerAdviceannotation is used to define a class that will handle exceptions globally across all controllers. Its methods are annotated with@ExceptionHandler,@InitBinder, and@ModelAttributeannotations. - The
@ExceptionHandlerannotation 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 !!
Hi Lokesh, I have a little confussion in this exception handling… i have a controller which have 5-8 @RequestMapping annotations(ModelAndView methods). if i use 1 @ExceptionHandler(Exception.class) for this controller, does it handles any exception which comes in this controller?
Yes. Please read again “register the method as exception handler for given exception and all of its subclasses.”
Hi Lokesh,
I appreciate your work on Spring 3 . Good work keep it up.
past 7 years I have been practicing java -j2ee working with reputed organization
I have few questions on Spring Annotation , would like to discuss with you ,please provide me your valuable reply on those .
1)@Configuration is introduced in Spring3 , which is the primary class reading that annotation and doing all necessary steps like instantiation , wiring and disposing bean .
2)pls observe below code snippet which does creation of bean and disposing bean
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package annotationparser;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
/**
*
* @author chinni
*/
public class AnnotationParser {
/**
* @param args the command line arguments
*/
public static void main(String[] args) throws ClassNotFoundException {
// TODO code application logic here
Class aimpl= (Class) Configuration.class.getClassLoader().loadClass(Configuration.class.getName());
Annotation a[]= aimpl.getDeclaredAnnotations();
for(Annotation m :a){
System.out.println(m.getDeclaredAnnotations()[0]);
}
}
}
using bove code we can get list of all configuration classes and we can instantiate object and return back
3)is it possible to invoke super class constructor using java.lang.reflect , if yes please send me the code
snippet
4) same question on this keyword
5) can we write annotations to inject Author and date time stamp on the fly ?
I am working on big project with Annotations , hence I have got all these questions . pls reply me back
Thanks
Sitaram Venkata
Hyderabad