Unified Exception Handling in Spring Boot: Reducing Try‑Catch Boilerplate with @ControllerAdvice, Assert Utilities, and Enum‑Based Error Codes
This article explains how to implement unified exception handling in Spring Boot using @ControllerAdvice and custom Assert utilities, demonstrates reducing repetitive try‑catch blocks with enum‑based error codes, and shows practical testing of various error scenarios including 404, validation, and database failures.
The article starts by describing the pain of excessive try { ... } catch { ... } finally { ... } blocks in Java services and the need for a cleaner way to handle exceptions.
What Is Unified Exception Handling
Spring 3.2 introduced @ControllerAdvice which can be combined with @ExceptionHandler to apply exception handling logic across all controllers, eliminating the need to repeat the same handler in each controller class.
Using Assert Instead of Explicit Throws
Instead of writing if (obj == null) { throw new IllegalArgumentException("msg"); } , the article recommends using Spring's org.springframework.util.Assert or a custom Assert interface that throws domain‑specific exceptions.
@Test
public void test1() {
User user = userDao.selectById(userId);
Assert.notNull(user, "用户不存在.");
...
}
@Test
public void test2() {
User user = userDao.selectById(userId);
if (user == null) {
throw new IllegalArgumentException("用户不存在.");
}
}The custom Assert interface provides default methods like assertNotNull(Object obj) that delegate to a newException method, allowing the exception type and message to be defined by an enum.
public abstract class Assert {
public static void notNull(@Nullable Object object, String message) {
if (object == null) {
throw new IllegalArgumentException(message);
}
}
}Enum‑Based Error Definitions
Two enums are introduced: IResponseEnum defines getCode() and getMessage() ; ResponseEnum implements BusinessExceptionAssert and provides concrete error codes such as BAD_LICENCE_TYPE(7001, "Bad licence type.") and LICENCE_NOT_FOUND(7002, "Licence not found.") .
public interface IResponseEnum {
int getCode();
String getMessage();
}
public enum ResponseEnum implements BusinessExceptionAssert {
BAD_LICENCE_TYPE(7001, "Bad licence type."),
LICENCE_NOT_FOUND(7002, "Licence not found.");
private int code;
private String message;
...
}Unified Exception Handler
A UnifiedExceptionHandler class annotated with @ControllerAdvice defines multiple @ExceptionHandler methods to process business exceptions, servlet‑related exceptions, validation errors, and generic exceptions. It also adapts messages based on the active Spring profile (production vs. development).
@Slf4j
@Component
@ControllerAdvice
@ConditionalOnWebApplication
public class UnifiedExceptionHandler {
private static final String ENV_PROD = "prod";
@Autowired
private UnifiedMessageSource unifiedMessageSource;
@Value("${spring.profiles.active}")
private String profile;
@ExceptionHandler(value = BusinessException.class)
@ResponseBody
public ErrorResponse handleBusinessException(BaseException e) { ... }
@ExceptionHandler({ NoHandlerFoundException.class, HttpRequestMethodNotSupportedException.class, ... })
@ResponseBody
public ErrorResponse handleServletException(Exception e) { ... }
@ExceptionHandler(value = BindException.class)
@ResponseBody
public ErrorResponse handleBindException(BindException e) { ... }
@ExceptionHandler(value = Exception.class)
@ResponseBody
public ErrorResponse handleException(Exception e) { ... }
}Unified Response Structure
All API responses share a base class containing code and message . Successful responses use CommonResponse (or its shortcut R ) with an optional data field, while error responses use ErrorResponse . Paginated results use QueryDataResponse (shortcut QR ).
Testing the Implementation
The article provides a series of manual tests (via browser and Postman) that verify the handling of:
Missing resources (404) – now throws NoHandlerFoundException after enabling spring.mvc.throw-exception-if-no-handler-found=true .
Unsupported HTTP methods – triggers HttpRequestMethodNotSupportedException .
Invalid request parameters – captured by MethodArgumentNotValidException and formatted into a single error message.
Business‑level errors such as LICENCE_NOT_FOUND and BAD_LICENCE_TYPE using the enum‑based Assert.
Database schema mismatches – result in generic server‑error responses in production.
Each test shows the JSON payload returned, containing the standardized code and message fields.
Extension and Internationalisation
The handler retrieves localized messages via UnifiedMessageSource using keys like response.BAD_LICENCE_TYPE . In production, sensitive stack traces are hidden and a generic "Network error" message is returned.
Conclusion
By combining a custom Assert interface, enum‑based error definitions, and a global @ControllerAdvice handler, most exception scenarios in a Spring Boot backend can be captured uniformly, reducing boilerplate and improving readability. The approach integrates easily with internationalisation and can be packaged as a reusable common library for future projects.
Architect's Tech Stack
Java backend, microservices, distributed systems, containerized programming, and more.
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.