受检异常必须显式处理,否则编译失败;应避免生吞异常,须记录日志、保留异常链或语义化包装,确保上游可追溯根因。

受检异常(Checked Exception)在 Java 中必须显式处理,否则编译不通过。但若处理方式粗暴——比如生吞异常、只打印堆栈却不重新抛出或封装,就容易切断异常链,导致上游无法获取原始错误上下文,排查时只见“空壳异常”,不见根因。
异常被吃掉:catch 后什么也不做
这是最典型的链断裂场景。例如在 DAO 层捕获 SQLException 后仅调用 e.printStackTrace(),既没记录日志,也没向上抛出或包装,调用方看到的可能是 NullPointerException 或业务逻辑错乱,完全不知道数据库连接已断。
- 务必避免空 catch 块,哪怕只是临时调试也要加注释说明原因
- 至少记录 ERROR 级别日志,包含异常完整堆栈和关键上下文(如 SQL、参数)
- 若当前层无法处理,应抛出更语义化的受检异常,并用
initCause(e)或构造函数传入原异常,保留链路
包装异常时丢失原始信息
为统一异常体系,常把底层异常包装成自定义业务异常。但如果只用 new BizException("数据库操作失败"),原始 SQLException 的堆栈、SQLState、错误码等全部丢失。
- 所有自定义异常构造函数应提供
Throwable cause参数,并调用super(message, cause) - 不要手动拼接消息掩盖 cause,比如
"DB error: " + e.getMessage()—— 这会让 getCause() 返回 null - 日志中优先打印
logger.error("xxx", e)(而非e.getMessage()),确保全链路堆栈输出
跨层传递时主动截断了 cause
有些框架或中间件(如早期 Spring JDBC 模板)会将 SQLException 转为 DataAccessException,但若配置不当或版本过旧,可能丢失原始 cause;还有人在 RPC 接口定义中把异常声明为 throws Exception,结果序列化时 cause 字段未被正确传输。
- 使用现代 Spring(5.0+)时,
JdbcTemplate默认保留原始 cause,可放心使用 - 自定义远程调用协议需确保异常类实现
Serializable,且所有字段(含 cause)可被序列化 - 对外 API 的 throws 声明尽量具体(如
throws OrderCreateFailedException),避免泛化 Exception
finally 中抛出新异常覆盖原有异常
当 try 块已发生异常,而 finally 块又抛出另一个异常(如资源关闭失败),JVM 会丢弃 try 中的异常,只抛出 finally 的异常——原始问题彻底消失。
- Java 7+ 推荐用 try-with-resources,自动抑制(suppressed)关闭异常,主异常仍可见
- 若手动写 finally,关闭资源时需用 try-catch 包裹,并用
addSuppressed()显式关联 - 避免在 finally 中执行可能抛异常的业务逻辑,专注资源清理
异常链不是装饰,是诊断的生命线。保留它不难,关键是每次 catch 都问一句:上游需要知道什么?











