Master Spring Global Exception & Data Handling with @ControllerAdvice
Learn how to use Spring's @ControllerAdvice, @ExceptionHandler, @InitBinder, and @ModelAttribute annotations to implement global exception handling, data binding, and pre-processing, including custom exceptions, order control with @Order, and practical code examples for robust backend development.
Introduction
In a front‑back separation project, backend developers need to return exception information and different response codes to the frontend for friendly prompts. Using @ControllerAdvice for global exception handling simplifies this, and the author further studied this annotation.
@ControllerAdvice Overview
@ControllerAdviceis a Spring annotation that enables centralized exception handling, global data binding, and pre‑processing. Combined with @ExceptionHandler, @InitBinder and @ModelAttribute, these three annotations are often called the "global three swords" for handling exceptions, data binding, and data pre‑processing.
"Global Three Swords"
@ExceptionHandler: Define methods in a @ControllerAdvice class to capture and handle specific exceptions. @InitBinder: Define methods to perform global data binding, such as converting request parameters to specific types. @ModelAttribute: Methods annotated with this are invoked before every request handler, allowing you to add common model data.
Internally, @ControllerAdvice is a meta‑annotation that wraps @Component:
@Target(ElementType.TYPE>
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ControllerAdvice {
@AliasFor("basePackages")
String[] value() default {};
@AliasFor("value")
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<?>[] assignableTypes() default {};
Class<? extends Annotation>[] annotations() default {};
}Classes annotated with @ControllerAdvice are scanned by Spring and registered as beans.
Global Exception Handling with @ExceptionHandler
Example controller that deliberately throws an exception:
@RestController
@RequestMapping("/api/v1/")
public class ExceptionTestController {
@GetMapping("/error")
public ResponseEntity<String> testException() {
int temp = 1 / 0;
return new ResponseEntity<>("请求成功", HttpStatus.OK);
}
}When this endpoint is called, an error page is displayed and the console logs the exception.
Adding a global exception handler:
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(ArithmeticException.class)
public ResponseEntity<String> bizException(ArithmeticException exception) {
log.error("异常信息:{}", exception.getMessage());
return new ResponseEntity<>("除数不能为0", HttpStatus.NOT_FOUND);
}
}The source of @ExceptionHandler:
/**
* Annotation for handling exceptions.
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExceptionHandler {
/**
* The exception types to handle. If empty, any exception declared in the method signature is handled.
*/
Class<? extends Throwable>[] value() default {};
}Because Throwable is the superclass of all exceptions, omitting the value attribute makes the method handle any exception.
Custom Exception
Define a custom exception class:
/**
* Custom exception
*/
public class MyException extends RuntimeException {
}Handle the custom exception globally:
@ExceptionHandler(MyException.class)
public ResponseEntity<String> myException(MyException exception) {
log.error("自定义异常信息:{}", exception.getMessage());
return new ResponseEntity<>("自定义异常信息", HttpStatus.BAD_REQUEST);
}
@GetMapping("/myerror")
public ResponseEntity<String> testMyException() {
throw new MyException();
}When the /myerror endpoint is called, the custom exception handler is invoked.
Multiple @ControllerAdvice Classes and @Order
If several @ControllerAdvice classes define handlers for the same exception, the one with the higher precedence (lower @Order value) is chosen. Example with two handlers:
@ControllerAdvice
@Slf4j
public class DivGlobalExceptionHandler {
@ExceptionHandler(ArithmeticException.class)
public ResponseEntity<String> bizException(ArithmeticException exception) {
log.error("进入DivGlobalExceptionHandler异常处理器,异常信息:{}", exception.getMessage());
return new ResponseEntity<>("除数不能为0", HttpStatus.NOT_FOUND);
}
}
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(ArithmeticException.class)
public ResponseEntity<String> bizException(ArithmeticException exception) {
log.error("进入GlobalExceptionHandler异常处理器,异常信息:{}", exception.getMessage());
return new ResponseEntity<>("除数不能为0", HttpStatus.NOT_FOUND);
}
}Testing shows that only one handler is invoked. By adding @Order, you can control which handler loads first:
@Order annotation controls component loading order; a smaller value means higher priority.
@ControllerAdvice
@Slf4j
@Order(1) // load first
public class GlobalExceptionHandler { ... }
@ControllerAdvice
@Slf4j
@Order(2) // load later
public class DivGlobalExceptionHandler { ... }Resulting behavior follows the loading order.
Global Data Binding with @InitBinder
The @InitBinder annotation allows you to customize data binding rules, such as converting string dates to Date objects or modifying string values before binding.
@InitBinder
public void initParam(WebDataBinder webDataBinder) {
// Convert string dates to Date objects
webDataBinder.registerCustomEditor(Date.class, new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), false));
// Append "qwe" to all incoming strings
webDataBinder.registerCustomEditor(String.class, new PropertyEditorSupport() {
@Override
public void setAsText(String text) throws IllegalArgumentException {
setValue(text + "qwe");
}
});
}
@GetMapping("/param")
public Map<String, Object> testParam(Date date, String str) {
Map<String, Object> map = new HashMap<>();
map.put("string", str);
map.put("date", date);
return map;
}Calling http://localhost:8000/api/v1/param?date=2024-5-9&str=123 returns the transformed values:
Global Data Pre‑Processing with @ModelAttribute
@ModelAttribute methods run before each request handler, allowing you to add common data to the model.
@ModelAttribute("uuid")
public String generateRandomUUID() {
return UUID.randomUUID().toString();
}
@GetMapping("/modelParam")
public Map<String, Object> testModelAttrParam(@ModelAttribute("uuid") String uuid) {
Map<String, Object> map = new HashMap<>();
map.put("uuid", uuid);
return map;
}Requesting /modelParam returns a generated UUID:
Another example adds a version attribute without specifying a name:
@ModelAttribute
public void addGlobalAttributes(Model model) {
model.addAttribute("version", "1.0.0");
}
@GetMapping("/modelParam")
public Map<String, Object> testModelAttrParam(@ModelAttribute("uuid") String uuid, Model model) {
Map<String, Object> map = new HashMap<>();
map.put("uuid", uuid);
map.put("version", model.asMap().get("version"));
return map;
}Calling the endpoint now returns both the UUID and the version:
In summary, @ControllerAdvice together with @ExceptionHandler, @InitBinder, and @ModelAttribute provides powerful mechanisms for global exception handling, data binding, and model preparation in Spring backend applications.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Xuanwu Backend Tech Stack
Primarily covers fundamental Java concepts, mainstream frameworks, deep dives into underlying principles, and JVM internals.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
