How to Eliminate 95% of Try‑Catch Blocks with Unified Exception Handling in Spring
This article explains why excessive try‑catch blocks hurt readability, introduces Spring's @ControllerAdvice and custom Assert utilities combined with enums to create a clean, centralized exception handling framework, and demonstrates the approach with full code examples and practical test scenarios.
In Java backend development, handling exceptions with repetitive try { ... } catch { ... } finally { ... } blocks quickly makes code noisy and hard to read. The author compares a typical messy controller to a clean one and argues that most of the boilerplate can be removed.
Background
Spring 3.2 added @ControllerAdvice, which can be paired with @ExceptionHandler, @InitBinder and @ModelAttribute to apply exception handling logic across all controllers. However, using @ExceptionHandler in every controller still leads to duplicated code.
Unified Exception Handling Principle
The idea is to classify exceptions by the stage at which they occur (before the Controller or inside the Service) and handle them centrally. A single @ControllerAdvice class can catch business exceptions, framework exceptions, and unknown exceptions, returning a uniform response structure containing code, message and optional data.
Replacing if Checks with Assert
Spring provides org.springframework.util.Assert for simple validation. The article shows a JUnit test that uses Assert.notNull(user, "User not found.") instead of an explicit
if (user == null) { throw new IllegalArgumentException(...); }check, improving readability.
public void test1() {
// ...
User user = userDao.selectById(userId);
Assert.notNull(user, "用户不存在.");
// ...
}The author then defines a custom Assert interface that creates domain‑specific exceptions via an Enum:
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);
}
}
}Enum‑Based Exception Definition
All custom exceptions inherit from BaseException and carry a code and message. An enum implementing both IResponseEnum and the custom Assert can generate the appropriate exception automatically:
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 final int code;
private final String message;
// getters omitted
@Override
default BaseException newException(Object... args) {
String msg = MessageFormat.format(this.getMessage(), args);
return new BusinessException(this, args, msg);
}
@Override
default BaseException newException(Throwable t, Object... args) {
String msg = MessageFormat.format(this.getMessage(), args);
return new BusinessException(this, args, msg, t);
}
}Using the enum, validation becomes a one‑liner:
ResponseEnum.LICENCE_NOT_FOUND.assertNotNull(licence);Centralized Exception Handler
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 that occur before the controller (e.g., NoHandlerFoundException, HttpRequestMethodNotSupportedException, MissingPathVariableException, etc.). handleBindException and handleValidException – process parameter‑binding and validation errors. handleException – a fallback for any unexpected exception.
@Slf4j
@Component
@ControllerAdvice
@ConditionalOnWebApplication
@ConditionalOnMissingBean(UnifiedExceptionHandler.class)
public class UnifiedExceptionHandler {
private static final String ENV_PROD = "prod";
@Autowired
private UnifiedMessageSource unifiedMessageSource;
@Value("${spring.profiles.active}")
private String profile;
// ... methods omitted for brevity ...
@ExceptionHandler(value = BusinessException.class)
@ResponseBody
public ErrorResponse handleBusinessException(BaseException e) {
log.error(e.getMessage(), e);
return new ErrorResponse(e.getResponseEnum().getCode(), getMessage(e));
}
// other handlers follow the same pattern
}The handler also adapts messages for production environments, returning a generic "Network error" instead of raw stack traces.
Unified Response Model
All API responses inherit from BaseResponse containing code and message. Successful responses use CommonResponse (adds data) or QueryDataResponse (adds pagination fields). For brevity, the project defines shortcut classes R<T> and QR<T> that wrap the common structures.
Verification Steps
The author creates a sample Spring Boot project ( spring-cloud-advance) with a LicenceService that demonstrates:
Fetching a licence by ID and using ResponseEnum.LICENCE_NOT_FOUND.assertNotNull(licence) to validate existence.
Paginated queries that validate the licence type with ResponseEnum.BAD_LICENCE_TYPE.assertNotNull(licenceTypeEnum).
POST requests that trigger parameter‑binding validation errors.
Simulated database schema mismatch to provoke an unknown exception.
Each scenario returns a JSON payload with a numeric code and a localized message, confirming that the unified handler works for business, framework, and unexpected errors.
Conclusion
By combining a custom Assert interface, enum‑based exception definitions, and a single @ControllerAdvice class, most exception handling code can be eliminated from controllers and services. The approach yields cleaner business logic, consistent error responses, and easy extensibility—simply add new enum entries for new error conditions.
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.
Liangxu Linux
Liangxu, a self‑taught IT professional now working as a Linux development engineer at a Fortune 500 multinational, shares extensive Linux knowledge—fundamentals, applications, tools, plus Git, databases, Raspberry Pi, etc. (Reply “Linux” to receive essential resources.)
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.
