Unified Exception Handling in Spring: @ControllerAdvice, Assert, and Enum‑Based Business Exceptions
This article explains how to centralize exception handling in Spring applications using @ControllerAdvice, replace repetitive null‑checks with Assert utilities, and combine enums with custom BusinessException classes to create a clean, maintainable backend error‑handling strategy.
During software development a large amount of try‑catch and finally blocks leads to redundant code and poor readability; a unified exception handling approach can centralize error processing and improve maintainability.
What is Unified Exception Handling?
Spring 3.2 introduced the @ControllerAdvice annotation, which works with @ExceptionHandler to apply exception‑handling methods across all controllers, eliminating the need to duplicate handling logic in each controller or to create a base controller class.
Practical Implementation
Before defining a unified handler, the article shows how to replace explicit null checks with Assert utilities:
@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("用户不存在.");
}
}Assert.notNull internally uses the same if‑null‑throw pattern, providing a cleaner API.
Enum‑Based Business Exceptions
By defining an interface IResponseEnum with getCode() and getMessage() , a custom BusinessException can be created that carries both a code and a message. The BusinessExceptionAssert interface extends IResponseEnum and Assert , providing default methods to create exceptions from enum values:
public interface IResponseEnum {
int getCode();
String getMessage();
}
public class BusinessException extends BaseException {
private static final long serialVersionUID = 1L;
public BusinessException(IResponseEnum responseEnum, Object[] args, String message) {
super(responseEnum, args, message);
}
public BusinessException(IResponseEnum responseEnum, Object[] args, String message, Throwable cause) {
super(responseEnum, args, message, cause);
}
}
public interface BusinessExceptionAssert extends IResponseEnum, Assert {
@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);
}
}
@Getter
@AllArgsConstructor
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, developers can throw specific exceptions without creating a separate class for each case, e.g., ResponseEnum.LICENCE_NOT_FOUND.assertNotNull(licence);
Unified Response Structure
The article also defines a common response hierarchy: BaseResponse contains code and message ; CommonResponse extends it with a data field; ErrorResponse represents failure results.
Validating the Unified Handler
A sample service demonstrates how to integrate the common package, perform null checks with the enum‑based asserts, and return DTOs:
@Service
public class LicenceService extends ServiceImpl
{
@Autowired
private OrganizationClient organizationClient;
public LicenceDTO queryDetail(Long licenceId) {
Licence licence = this.getById(licenceId);
checkNotNull( licence );
OrganizationDTO org = ClientUtil.execute(() -> organizationClient.getOrganization(licence.getOrganizationId()));
return toLicenceDTO(licence, org);
}
private void checkNotNull(Licence licence) {
ResponseEnum.LICENCE_NOT_FOUND.assertNotNull(licence);
}
// other methods omitted for brevity
}Conclusion
Combining Assert utilities, enum‑based BusinessException, and @ControllerAdvice enables most business exceptions to be captured and returned as a unified {code, message, data} structure, while also simplifying the addition of new error types without proliferating exception classes.
When security, gateway degradation, or remote‑call failures are involved, additional handling may be required, and internationalization should be applied to the returned messages.
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.