Mastering Java Exception Handling and Global Error Management in Spring Boot

This article explains Java's throw‑catch mechanism, the three core principles of exception handling, and demonstrates how to design a custom exception hierarchy and a Spring Boot @ControllerAdvice‑based global handler to simplify error processing and improve code maintainability.

Architect's Alchemy Furnace
Architect's Alchemy Furnace
Architect's Alchemy Furnace
Mastering Java Exception Handling and Global Error Management in Spring Boot

In Java applications, exception handling follows a throw‑catch mechanism: methods create exception objects and either handle them locally or propagate them up the call stack until a suitable handler is found.

The three principles of Java exception handling are specificity, early throwing (fail‑fast), and delayed catching.

Example code shows a typical service method that logs and returns a ResponseResult while catching JSON processing, interruption and execution exceptions.

@OperateLogAnnotation(moduleName = "发送命令", methodDesc = "发送命令")
public ResponseResult sendCmd(@RequestBody SendCmdParam param) {
    BaseResp baseResp = new BaseResp(ResultType.OTHER_ERROR);
    ObjectMapper objectMapper = new ObjectMapper();
    try {
        String json = objectMapper.writeValueAsString(param);
        template.send(cmdTopicName, String.valueOf(System.currentTimeMillis()), json).get();
        baseResp.setResultType(ResultType.SUCCESS);
    } catch (JsonProcessingException e) {
        logger.error("serialization error.", e);
        baseResp.setCustomErrMsg(e.getMessage());
    } catch (InterruptedException e) {
        logger.error("sync send message fail.", e);
        baseResp.setCustomErrMsg(e.getMessage());
    } catch (ExecutionException e) {
        logger.error("sync send message fail.", e);
        baseResp.setCustomErrMsg(e.getMessage());
    }
    return this.setResult(baseResp);
}

To avoid repetitive try‑catch blocks, a custom exception hierarchy is introduced. The base class BaseRuntimeException provides constructors for message, error code and cause.

public class BaseRuntimeException extends RuntimeException {
    private String errorCode;
    private String errorMsg;

    /** Exception base constructor */
    public BaseRuntimeException(String errorMsg) {
        super(errorMsg);
        this.errorMsg = errorMsg;
    }

    /** Exception base constructor with cause */
    public BaseRuntimeException(String errorMsg, Throwable cause) {
        super(errorMsg, cause);
        this.errorMsg = errorMsg;
    }

    /** Exception base constructor with error code */
    public BaseRuntimeException(String errorCode, String errorMsg) {
        super(errorCode);
        this.errorCode = errorCode;
        this.errorMsg = errorMsg;
    }

    /** Exception base constructor with error code and cause */
    public BaseRuntimeException(String errorCode, String errorMsg, Throwable cause) {
        super(errorCode, cause);
        this.errorCode = errorCode;
        this.errorMsg = errorMsg;
    }
}

A global exception handler is implemented with Spring Boot’s @ControllerAdvice. It defines methods annotated with @ExceptionHandler to handle generic Exception, custom BaseRuntimeException, AccessDeniedException, NullPointerException, DataNotFoundException, FieldValueExistsException, PersistenceException, UnauthorizedRuntimeException, and validation errors.

@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    @ResponseBody
    @ExceptionHandler(Exception.class)
    public ResponseResult<?> errorHandler(Exception ex) {
        log.error("接口出现严重异常:{}", ExceptionUtils.getStackTrace(ex));
        return new ResponseResult<>(ResultType.SYS_SO_BUSY, "服务异常,请稍后再试!", Lists.newArrayList());
    }

    @ResponseBody
    @ExceptionHandler(BaseRuntimeException.class)
    public ResponseResult bizExceptionHandler(BaseRuntimeException ex) {
        log.error("发生业务异常!错误代码:{}", ex.getErrorCode().concat(ex.getErrorMsg()));
        log.error("发生业务异常!堆栈是:{}", ExceptionUtils.getStackTrace(ex));
        return new ResponseResult<>(ResultType.valueOf(ex.getErrorCode()), ex.getErrorMsg(), Lists.newArrayList());
    }

    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    @ResponseBody
    @ExceptionHandler(AccessDeniedException.class)
    public ResponseResult<?> accessDeniedExceptionHandler(Exception ex) {
        log.error("接口出现严重异常:{}", ExceptionUtils.getStackTrace(ex));
        return new ResponseResult<>(ResultType.USER_NO_AUTH, "无该接口访问权限!", Lists.newArrayList());
    }

    // Additional handlers for NullPointerException, DataNotFoundException, FieldValueExistsException,
    // PersistenceException, UnauthorizedRuntimeException, MethodArgumentNotValidException, etc.
}

The unified response format is encapsulated in the ResponseResult class, which carries a status code, message and data payload.

@Data
@NoArgsConstructor
@ApiModel(value = "ResponseResult", description = "返回对象基类")
public class ResponseResult<T> implements ResponseEntity {
    @ApiModelProperty(value = "返回结果值 0:成功, 1:参数不正确, ...", required = true)
    private int status;
    @ApiModelProperty(value = "返回结果描述", required = true)
    private String msg;
    @ApiModelProperty(value = "返回结果对象", required = true)
    private List<T> data;

    public ResponseResult(ResultType resultType, List<T> data) {
        this.status = resultType.getValue();
        this.msg = resultType.getDesc();
        this.setData(data);
    }

    public ResponseResult(ResultType resultType, String errMsg, List<T> data) {
        this.status = resultType.getValue();
        this.msg = errMsg;
        this.setData(data);
    }

    public ResponseResult(int status, String errMsg, List<T> data) {
        this.status = status;
        this.msg = errMsg;
        this.setData(data);
    }

    public void setData(List<T> data) {
        if (data != null && data.size() == 1 && data.get(0) == null) {
            data = new ArrayList<>();
        }
        this.data = data;
    }
}

For convenient access to the current logged‑in user, a custom @CurrentUser annotation and a corresponding CurrentUserMethodArgumentResolver are presented. The resolver extracts user information from the request token, validates required roles, and injects a BaseParam instance into controller methods.

public class CurrentUserMethodArgumentResolver implements HandlerMethodArgumentResolver {
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(CurrentUser.class);
    }

    @Override
    public BaseParam resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                                      NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        if (!BaseParam.class.isAssignableFrom(parameter.getParameterType())) {
            throw new ReturnValuesRuntimeException(ResultType.OTHER_ERROR, "API接口参数类型错误,请联系管理员处理!");
        }
        BaseParam param = getAuthRequestObject();
        if (param == null) {
            throw new UnauthorizedRuntimeException("获取用户信息失败");
        }
        for (Annotation annotation : parameter.getParameterAnnotations()) {
            if (annotation instanceof CurrentUser) {
                CurrentUser currentUser = (CurrentUser) annotation;
                int[] roles = currentUser.roles();
                if (roles.length > 0 && Arrays.stream(roles).noneMatch(v -> String.valueOf(v).equals(param.getCurrRoleId()))) {
                    throw new UnauthorizedRuntimeException("没有权限");
                }
            }
        }
        return param;
    }

    protected <F> F getAuthRequestObject() {
        // ... implementation omitted ...
        return null;
    }
}

By consolidating exception handling, response formatting, and user context injection, the code becomes cleaner, easier to maintain, and provides a consistent error‑reporting experience for API consumers.

Javabackend developmentexception-handlingSpring BootGlobal Error Handling
Architect's Alchemy Furnace
Written by

Architect's Alchemy Furnace

A comprehensive platform that combines Java development and architecture design, guaranteeing 100% original content. We explore the essence and philosophy of architecture and provide professional technical articles for aspiring architects.

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.