throw new RuntimeException(e) 会丢原始堆栈,因为 e 被当作消息字符串而非 cause 传入,导致 getCause() 返回 null、getStackTrace() 变短;正确写法是 throw new RuntimeException("msg", e)。

为什么 throw new RuntimeException(e) 会丢原始堆栈?
直接用 new RuntimeException(e) 包装异常时,e 被当作「消息字符串」传入,而不是「cause」。JVM 不会自动建立异常链,e.getStackTrace() 在新异常里不可见,调用 getCause() 也返回 null。
- 错误写法:
throw new RuntimeException("处理失败", e); // ✅ 正确:第二个参数是 cause - 常见误写:
throw new RuntimeException(e); // ❌ 错:e.toString() 被当 message,丢失堆栈和 cause 关系
- 验证方式:打印
e.getCause()和e.getStackTrace().length,前者为null、后者明显变短即中招
Java 7+ 推荐用 Throwable.addSuppressed() 处理多异常
当 try-with-resources 或 finally 块中又抛出异常(比如关闭流失败),原始异常可能被覆盖。用 addSuppressed() 可显式保留主异常 + 补充异常,避免信息丢失。
- 适用场景:资源关闭失败、日志记录异常、兜底清理逻辑出错
-
try-with-resources已自动调用addSuppressed(),但手动finally中需自己加 - 示例:
try { doSomething(); } catch (IOException e) { throw e; // 主异常 } finally { try { resource.close(); } catch (IOException suppressed) { e.addSuppressed(suppressed); // ✅ 显式附加 } }
自定义异常类必须重载带 cause 的构造函数
如果定义了 MyBusinessException,但只写了 public MyBusinessException(String msg),那上游用 new MyBusinessException("xxx", e) 会编译失败 —— 因为没声明接收 Throwable 的构造器。
- 必须补全:
public class MyBusinessException extends Exception { public MyBusinessException(String message) { super(message); } public MyBusinessException(String message, Throwable cause) { super(message, cause); // ✅ 关键:调用父类双参构造 } } - 不补全的后果:被迫用
initCause()(不安全,可能被多次调用)或退化为无链包装 - Lombok 的
@AllArgsConstructor不会自动包含cause参数,需显式声明或搭配@SuperBuilder
日志打印时别只打 e.getMessage()
很多日志语句写成 log.error("操作失败: {}", e.getMessage()),这只会输出最外层异常的消息,完全看不到原始异常类型、堆栈、甚至 cause 链。
立即学习“Java免费学习笔记(深入)”;
- 正确做法:用
log.error("操作失败", e)(SLF4J / Log4j2 支持自动打印完整链) - 若必须拼字符串,至少用
e.toString(),它默认包含cause的简要信息(但不如完整堆栈) - 注意:
e.printStackTrace()到System.err不进日志系统,线上环境基本无效
catch 的重抛方式、以及日志框架对 Throwable 的原生支持。漏掉任意一环,排查时就只剩一个孤零零的「null pointer」在日志里。










