重抛异常的核心目的是让当前层有限干预后原样交还调用方,必须用 throw e; 保留原始类型、堆栈和cause链;throw new RuntimeException(e); 会截断堆栈,导致定位困难。

重抛异常的核心目的,是让当前层做有限干预(比如记录日志、清理资源),再把异常原样交还给调用方处理。关键在于“不破坏原始信息”——类型、堆栈、cause链都得留着,否则上层就失去了准确定位问题的能力。
为什么必须用 throw e,而不是 throw new RuntimeException(e)
前者是原封不动地把捕获到的异常对象再次抛出;后者会新建一个异常,导致原始堆栈被截断,printStackTrace() 显示的是包装处的位置,而非真正出错的地方。尤其在排查分布式调用或异步链路时,丢失原始堆栈会让问题定位慢好几倍。
- 正确写法:
throw e;—— 堆栈指向最初抛出处 - 错误写法:
throw new RuntimeException(e);—— 堆栈从这行开始重置 - 若真要包装,应继承原始异常类型,或确保构造函数传入
e作为 cause
日志记录后重抛的典型场景
不是所有异常都要当场吞掉。比如数据库操作失败,你可能想记下 SQL 和参数,但不自己重试或降级,而是交由服务层统一兜底。
- 先
log.error("DB execute failed, sql: {}, params: {}", sql, params, e); - 紧接着
throw e;,不要加任何判断或延迟 - 避免在
finally块里 throw —— 它会覆盖 try/catch 中已抛出的异常,造成根因丢失
检查型异常重抛需注意方法签名
Java 中,如果 catch 的是 IOException 这类检查型异常,而当前方法没在声明中写 throws IOException,直接 throw e; 会编译失败。
- 方案一:在方法签名补上
throws IOException - 方案二:转为非检查型异常,如
throw new UncheckedIOException(e);(JDK 7+ 提供) - 不推荐:
throw new RuntimeException(e);—— 除非你明确接受调试线索变弱的代价
重抛时要不要调用 fillInStackTrace()?
一般不用。它的作用是把当前抛出点设为新堆栈起点,适用于“异常经过多层传递后,想掩盖中间路径”的特殊封装场景。日常日志+重抛不需要它;加了反而让堆栈失真,不利于回溯。
- 默认
throw e;保留原始堆栈,最安全 -
e.fillInStackTrace()是主动重置,仅在封装异常并希望隐藏传播路径时考虑 - 多数监控系统和日志平台依赖原始堆栈做聚合与告警,随意重置会影响问题归因

















