Spring Boot Global Exception Handler Example

By Arvind Rai, 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 {
} 
In this cace all controllers of package com.cp.controller and its sub-packgaes will be served by above class.
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) {
      ------
   }
   ------
} 
The above method 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);
	}
} 
EmployeeNotFoundException.java
public class EmployeeNotFoundException extends RuntimeException {
	private static final long serialVersionUID = 1L;

	public EmployeeNotFoundException(String errorMessage) {
		super(errorMessage);
	}
} 
InvalidEmployeeDataException.java
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.
Spring Boot Global Exception Handler Example

8. References

9. Download Source Code

POSTED BY
ARVIND RAI
ARVIND RAI
LEARN MORE








©2024 concretepage.com | Privacy Policy | Contact Us