Spring Boot Global Exception Handler Example
October 16, 2023
On this page we will learn to create global exception handler in our Spring Boot application. Global exception handler is a common exception handler for more than one Spring controllers. To create it, we need to understand Spring @ControllerAdvice
and @ExceptionHandler
annotations.
1. @ControllerAdvice Annotation
1. The
@ControllerAdvice
is the specialization of @Component
stereotype annotation.
2. The class annotated with
@ControllerAdvice
listens multiple controller class annotated with @Controller
. These classes can be declared explicitly as Spring beans or auto-detected via classpath scanning.
3. The
@ControllerAdvice
is used to create global exception handler class that contains methods annotated with @ExceptionHandler
.
4. The
@ControllerAdvice
is also used to create global classes containing the methods annotated with @InitBinder
and @ModelAttribute
.
5. All the classes annotated with
@ControllerAdvice
are ordered on the basis of Spring @Order
or Jakarta @Priority
annotations.
6. The elements of
@ControllerAdvice
are annotations, assignableTypes, basePackageClasses, basePackages, value.
7. basePackages element is assigned with array of packages that will be base package of controllers included for this
@ControllerAdvice
class. Suppose we have base package as given below.
@ControllerAdvice(basePackages = { "com.cp.controller" }) public class GlobalControllerAdvice { }
For other controllers that are in different package, we can create another class annotated with
@ControllerAdvice
.
@ControllerAdvice(basePackages = { "com.cp.restcontroller" }) public class GlobalRestControllerAdvice { }
2. @ExceptionHandler Annotation
The@ExceptionHandler
marks a method that handles exception. It accepts the value as Class<? extends Throwable>
.
We create exception handling method as following.
@ControllerAdvice(basePackages = { "com.cp.controller" }) public class GlobalControllerAdvice { @ExceptionHandler(Exception.class) public ModelAndView handleFileNotFoundException(Exception exception) { ------ } ------ }
handleFileNotFoundException()
will handle exception type Exception
and will propagate to the bottom child exception class.
1. Arguments : The arguments for
@ExceptionHandler
method are an exception argument, request and response object, session object, WebRequest/NativeWebRequest, Locale, InputStream / Reader, OutputStream / Writer, Model .
2. Return type :
The return type for
@ExceptionHandler
method are ModelAndView, Model, Map, View, String, @ResponseBody, HttpEntity<?> or ResponseEntity<?>, void . The return type ‘void’ is used when exception is handled by method itself.
3. Creating Exception Classes to be Handled Globally
Find some custom exception classes used in our application that I will handle globally.DuplicateEmployeeException.java
public class DuplicateEmployeeException extends RuntimeException { private static final long serialVersionUID = 1L; public DuplicateEmployeeException(String errorMessage) { super(errorMessage); } }
public class EmployeeNotFoundException extends RuntimeException { private static final long serialVersionUID = 1L; public EmployeeNotFoundException(String errorMessage) { super(errorMessage); } }
public class InvalidEmployeeDataException extends Exception { private static final long serialVersionUID = 1L; public InvalidEmployeeDataException(String errorMessage) { super(errorMessage); } }
4. Creating Global Exception Handler
Find the controller advice class that is handling all the exceptions globally thrown by controller.GlobalControllerAdvice.java
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.servlet.ModelAndView; import com.concretepage.controller.EmployeeController; @ControllerAdvice(basePackages = { "com.concretepage.controller" }) public class GlobalControllerAdvice { private static final Logger logger = LoggerFactory.getLogger(EmployeeController.class); @ExceptionHandler(EmployeeNotFoundException.class) public ModelAndView handleEmployeeNotFoundException(EmployeeNotFoundException exception) { logger.error(exception.getMessage()); return displayErrorPage("Error: Employee not found."); } @ExceptionHandler(DuplicateEmployeeException.class) public ModelAndView handleDuplicateEmployeeException(DuplicateEmployeeException exception) { logger.error(exception.getMessage()); return displayErrorPage("Error: Duplicate employee."); } @ExceptionHandler(InvalidEmployeeDataException.class) public ModelAndView handleInvalidEmployeeDataException(InvalidEmployeeDataException exception) { logger.error(exception.getMessage()); return displayErrorPage("Error: Invalid employee data"); } @ExceptionHandler(Exception.class) public ModelAndView handleException(Exception exception) { logger.error(exception.getMessage()); return displayErrorPage("An error occurred."); } ModelAndView displayErrorPage(String errorMsg) { ModelAndView mav = new ModelAndView(); mav.addObject("errMessage", errorMsg); mav.setViewName("global-error"); return mav; } }
5. Creating Controller
Find my controller. Here I have thrown exception conditionally to be handled globally.EmployeeController.java
@Controller @RequestMapping("app") public class EmployeeController { private static final Logger logger = LoggerFactory.getLogger(EmployeeController.class); @Autowired private EmployeeService empService; @PostMapping("create-emp") public ModelAndView createEmployee(@Valid @ModelAttribute("emp") Employee emp, BindingResult result) throws IOException, InvalidEmployeeDataException { ModelAndView mav = new ModelAndView(); if (emp.getEmpId() == null) { if (empService.getEmployeeIdByNameNProfile(emp.getEmpName(), emp.getProfile()) == 0) { throw new DuplicateEmployeeException("Employee already exists."); } empService.addEmployee(emp); } else { if (!empService.isEmpDataValid(emp)) { throw new InvalidEmployeeDataException("Employee data invalid."); } empService.updateEmployee(emp); } mav.addObject("allEmps", empService.getAllEmployees()); mav.setViewName("emp-info"); logger.info("Form submitted successfully."); return mav; } @GetMapping("get-emp") public ModelAndView getEmp(@RequestParam("id") Integer empId) { if (empService.getEmployeeByI(empId) == null) { throw new EmployeeNotFoundException("Employee not Found"); } ModelAndView mav = new ModelAndView(); mav.setViewName("new-employee"); mav.addObject("emp", empService.getEmployeeByI(empId)); mav.addObject("allProfiles", getProfiles()); return mav; } }
6. Creating Global Error Page
I am creating a global error page using Thymeleaf.global-error.html
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"> <head> <title th:text="#{global-error-page}"> Global Error Page </title> </head> <body> <h3 th:text="${errMessage}">Message</h3> </body> </html>
7. Output
When we run application, we get global error messages as below.