Java 异常日志:printStackTrace 与 getMessage 的差别及正确写法
printStackTrace() 和 getMessage() 是 Java 异常处理的两个基础方法,但很多人把它们当成“打日志”的唯一手段,结果线上出问题只能看到一行 NullPointerException,既没有原因也没有上下文。本文说明两者的信息差异、正确的日志写法与性能注意点。

一、三个方法各自输出什么
try {
int r = 10 / divisor;
} catch (ArithmeticException e) {
e.getMessage(); // "/ by zero" 仅异常消息文本
e.toString(); // "java.lang.ArithmeticException: / by zero"
e.printStackTrace(); // 完整堆栈,输出到 System.err
}
关键差异:getMessage() 只有一句话,甚至可能返回 null(很多异常没带消息,如未经构造参数创建的 NPE);printStackTrace() 才能看到“哪个类的哪一行抛出、经由哪些调用栈、是否由其他异常引起”。只看 message 排查,等于丢掉 90% 的定位信息。
二、printStackTrace 的三个问题
- 输出目标是
System.err,不走日志框架,容器/线上环境常被丢弃或与其他日志混排。 - 没有时间与级别,无法按 traceId 关联业务请求。
- 吞掉 cause 的风险:自定义异常包装时若没传
cause,堆栈链条就断了。
三、正确做法:交给日志框架
// SLF4J / Logback:把异常对象作为最后一个参数传入,不要拼接字符串
private static final Logger log = LoggerFactory.getLogger(XxxService.class);
try {
orderMapper.insert(order);
} catch (DataAccessException e) {
log.error("下单失败, userId={}, orderId={}", userId, orderId, e);
// 注意:e 放在最后且不占位,Logback 会自动打印完整堆栈
}
// 反例(会丢掉堆栈)
log.error("下单失败: " + e.getMessage());
log.error("下单失败: {}", e); // 只调 toString,堆栈不输出
这是 Java 日志最经典的误用:把异常写进占位符而不是作为最后一个参数,结果日志里只有一行 toString()。SLF4J 的规则是:最后一个参数为 Throwable 且未与之对应的占位符时,输出完整堆栈。
四、异常链与 cause 的正确写法
public class OrderException extends RuntimeException {
public OrderException(String msg, Throwable cause) { super(msg, cause); }
}
try {
pay();
} catch (IOException e) {
throw new OrderException("支付阶段失败", e); // 必须传 cause
}
传了 cause,日志会打印 Caused by: java.io.IOException: ... 及其原始堆栈,形成完整链条;漏传则真正的错误来源永远看不到。
五、性能与线上注意事项
- 填充堆栈是昂贵的:
Throwable.fillInStackTrace()需要遍历调用栈,在高频循环里抛异常性能损失显著。可用重写fillInStackTrace返回 this 的轻量异常(例如高频校验类异常),或改用返回值/Optional。 - 不要吞异常:
catch (Exception e) {}空捕获是最危险的写法,至少要log.warn。 - 日志级别:可恢复的用
warn,影响业务的用error;不要把正常的“未找到记录”打成 error 造成告警噪音。 - 输出到字符串(测试/上报场景):
public static String stackToString(Throwable t) {
StringWriter sw = new StringWriter();
t.printStackTrace(new PrintWriter(sw));
return sw.toString();
}
Web 场景常用它把堆栈写入监控上报,但注意敏感信息脱敏后再外发。
六、JDK 新特性简述
Java 14 起默认开启有用异常信息(Helpful NullPointerException),NPE 消息会明确指出哪个变量为 null(形如 Cannot invoke "String.length()" because "s" is null),老版本只给一个空消息。getMessage() 的实用度因此大幅提升;若跑在 JDK 11 及以下且需要该特性,可加 -XX:+ShowCodeDetailsInExceptionMessages(Java 15 后默认开启)。
七、常见问题 FAQ
问:printStackTrace 和 log.error 可以混用吗?
答:生产代码应统一走日志框架,printStackTrace 只适合本地临时调试。
问:怎么让日志带 traceId?
答:用 MDC:MDC.put("traceId", id),并在 appender pattern 里加 %X{traceId}。
问:catch 到 Exception 再抛 RuntimeException 行不行?
答:可以,但一定传 cause;同时注意事务回滚行为(Spring 默认只对 RuntimeException 回滚)。
结论:getMessage() 只用于给用户看的提示文案,绝不能用它替代堆栈定位;日志请写成 log.error("业务描述, k1={}", v1, e),包装异常务必传 cause,高频路径少抛异常。
生产内容,同时打通搜索引擎(SEO)与AI搜索引擎(GEO)流量入口的软件! 点击进入365建站器 »
