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