Unified Exception Handling in Spring: Using @ControllerAdvice, Assert, and Enum for Clean Code
This article explains how to replace repetitive try‑catch blocks in Java Spring applications with a unified exception handling mechanism that leverages @ControllerAdvice, custom Assert utilities, and an enum‑based error‑code system to produce clean, maintainable backend code.
Background – In typical Java projects a large amount of boilerplate try { … } catch { … } finally { … } code appears, especially in Controllers and Services, which hurts readability.
The author compares an ugly try‑catch style with a clean Controller design and argues for moving exception handling out of business code.
What is Unified Exception Handling?
Since Spring 3.2 the @ControllerAdvice annotation can be combined with @ExceptionHandler , @InitBinder , and @ModelAttribute to apply exception handling globally.
Only @ExceptionHandler actually deals with exceptions. By defining a single class annotated with @ControllerAdvice , you can catch all controller‑level and service‑level exceptions in one place.
Goal
Eliminate more than 95% of explicit try‑catch blocks by using an Assert (assertion) style that throws custom business exceptions, allowing developers to focus on business logic.
Practical Implementation
1. Assert Replacement – Use Spring's org.springframework.util.Assert (or a custom one) to replace manual if (obj == null) { throw new … } checks.
@Test
public void test1() {
User user = userDao.selectById(userId);
Assert.notNull(user, "User does not exist.");
}
public void test2() {
User user = userDao.selectById(userId);
if (user == null) {
throw new IllegalArgumentException("User does not exist.");
}
}The custom Assert interface defines newException methods so that the thrown exception can be a domain‑specific BusinessException with an error code and message.
public interface Assert {
BaseException newException(Object... args);
BaseException newException(Throwable t, Object... args);
default void assertNotNull(Object obj) {
if (obj == null) {
throw newException(obj);
}
}
default void assertNotNull(Object obj, Object... args) {
if (obj == null) {
throw newException(args);
}
}
}Business exceptions are defined by an enum that implements BusinessExceptionAssert (which extends IResponseEnum and Assert ), e.g.:
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;
}Using the enum you can write concise validation code such as:
ResponseEnum.LICENCE_NOT_FOUND.assertNotNull(licence);
ResponseEnum.BAD_LICENCE_TYPE.assertNotNull(licenceTypeEnum);Unified Exception Handler Class
The UnifiedExceptionHandler class is annotated with @ControllerAdvice and defines several @ExceptionHandler methods:
handleBusinessException – catches BusinessException .
handleBaseException – catches other custom BaseException s.
handleServletException – catches framework exceptions such as NoHandlerFoundException , HttpRequestMethodNotSupportedException , etc., and maps them to a generic error code in production.
handleBindException and handleValidException – process parameter‑binding and validation errors, aggregating field messages.
handleException – a fallback for any unknown exception, returning a generic server‑error response (or a user‑friendly "Network error" in production).
Each handler logs the exception, obtains a localized message via UnifiedMessageSource , and returns an ErrorResponse containing code and message .
Exception Classification
Exceptions are divided into two major groups:
Pre‑Controller exceptions (e.g., 404, method not allowed, missing parameters) handled by handleServletException .
Service‑level exceptions – custom business exceptions handled by handleBusinessException / handleBaseException and unknown exceptions handled by handleException .
Testing the Mechanism
Various scenarios are demonstrated: missing licence, invalid licence type, 404, unsupported HTTP method, parameter validation failures, and database errors caused by a mismatched entity field. All cases return a JSON payload with code and message .
Conclusion
By combining assertions, an enum‑based error‑code system, and a global @ControllerAdvice handler, most exceptions become easy to manage, the code stays clean, and the API returns consistent error structures. The approach can be packaged as a common library and reused across projects.
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
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.