Java异常处理需精准分类、合理抛出与捕获:检查型异常应封装为带业务语义的自定义异常,log.error与throw必须同时出现,异步场景须配置全局异常处理器,自定义异常应按职责继承Exception或RuntimeException并显式声明serialVersionUID。

Java中异常处理不当直接导致可维护性下降
不是“用了try-catch就安全了”,而是异常分类、抛出时机、捕获粒度这三处写错,会让后续所有人改代码时如履薄冰。比如把NullPointerException吞掉却不记录日志,或者在Service层用throws Exception强制上层全盘兜底,都会让问题定位变慢、修改成本翻倍。
检查型异常(Checked Exception)该不该用
Java强制你处理的IOException、SQLException等,本意是提醒“这里可能出事”,但滥用会污染业务逻辑。常见错误包括:
- 在DTO或VO类里声明
throws IOException——它们根本不该碰IO - 为每个DAO方法都加
throws SQLException,却不在Service层统一转换成业务异常 - 用
catch (Exception e) { throw new RuntimeException(e); }粗暴转成运行时异常,丢失原始类型和上下文
合理做法是:数据访问层捕获具体异常,封装为带业务语义的自定义异常(如OrderNotFoundException),并确保异常消息包含关键ID或参数值。
log.error()和throw new XxxException()必须同时出现
只记录日志不抛出,调用方收不到失败信号;只抛异常不记录,线上出问题时连堆栈都没留下。尤其注意以下场景:
立即学习“Java免费学习笔记(深入)”;
- 异步线程中未设置
Thread.setDefaultUncaughtExceptionHandler,异常静默消失 - 使用
CompletableFuture时,exceptionally()里只返回默认值,没记日志 - Spring AOP切面捕获异常后,仅
return null,没调用log.error("xxx failed", e)
一个最小可靠模式:log.error("Failed to process order {}, reason: {}", orderId, e.getMessage(), e); throw new OrderProcessFailedException(orderId, e);
自定义异常别只继承Exception,要分清职责
所有自定义异常都extends Exception,等于放弃异常分类能力。实际应按行为区分:
- 需要上游明确处理的(如库存不足)→ 继承
Exception,强制调用方决策 - 属于系统故障或不可恢复错误(如数据库连接断开)→ 继承
RuntimeException,避免层层throws污染API - 需携带额外上下文(如订单号、用户ID)→ 在构造函数中接收并保存为final字段,不要依赖getMessage()拼接
最容易被忽略的一点:自定义异常类本身要serialVersionUID显式声明,否则集群环境下反序列化可能失败。










