Backend Development 22 min read

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.

Architect's Tech Stack
Architect's Tech Stack
Architect's Tech Stack
Unified Exception Handling in Spring Boot: Reducing Try‑Catch Boilerplate with @ControllerAdvice, Assert Utilities, and Enum‑Based Error Codes

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.

backendJavaException HandlingSpringenumAssertUnified Response
Architect's Tech Stack
Written by

Architect's Tech Stack

Java backend, microservices, distributed systems, containerized programming, and more.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.