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.

Liangxu Linux
Liangxu Linux
Liangxu Linux
How to Eliminate 95% of Try‑Catch Blocks with Unified Exception Handling in Spring

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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

BackendJavaException HandlingspringenumAssertUnified
Liangxu Linux
Written by

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.)

0 followers
Reader feedback

How this landed with the community

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.