不能用 catch (throwable e),因为它会捕获包括 outofmemoryerror 等不可恢复的 jvm 错误,掩盖系统级故障,导致数据损坏或死锁;应精确捕获具体 exception 子类并显式声明。

为什么不能用 catch (Throwable e)
因为 Throwable 包含 Error(如 OutOfMemoryError、StackOverflowError)和 Exception,而 Error 表示 JVM 无法恢复的严重问题。捕获它不仅掩盖了系统级故障,还可能让程序在资源耗尽或状态错乱的情况下继续运行,引发更隐蔽的数据损坏或线程死锁。
常见错误现象:应用内存持续增长后突然卡死,日志里只有一条被“吃掉”的 OutOfMemoryError;或者递归过深时静默失败,后续逻辑读到 null 或非法状态。
- 真正该捕获的是
Exception及其子类(不包括RuntimeException的话,按需) -
Error不是设计来被应用层处理的——JVM 都扛不住,你的代码更不该假装能兜底 - IDE 和静态检查工具(如 SpotBugs)会直接标红
catch (Throwable),这不是风格问题,是明确的风险信号
catch (Exception e) 也得看场景
泛捕 Exception 比 Throwable 好,但依然危险。它会吞掉本该向上抛的受检异常(比如 IOException),也可能掩盖未预期的运行时异常(比如 NullPointerException),导致错误位置偏移、调试困难。
使用场景有限:仅适用于最外层兜底(如 Servlet 的 doGet、Spring 的 @ExceptionHandler)、或明确要记录+忽略的已知可恢复异常(如某次 HTTP 调用超时)。
立即学习“Java免费学习笔记(深入)”;
- 不要在工具方法里写
catch (Exception e)—— 它该把异常交给调用方决定怎么处理 - 如果真要兜底,至少重新抛出包装后的异常:
throw new RuntimeException("HTTP call failed", e) - 注意
Exception包含RuntimeException,所以你 catch 住的可能是空指针,却没意识到是自己代码 bug
替代方案:精确捕获 + 显式声明
最佳实践不是“少捕获”,而是“捕获得刚刚好”:只捕你要处理的异常类型,并通过方法签名暴露受检异常,让调用方无法忽视。
例如文件操作,与其写 try { ... } catch (Exception e) { log(e); },不如:
public String readFile(String path) throws IOException {
return Files.readString(Paths.get(path));
}
这样调用方必须处理 IOException,或显式声明上抛。既不掩盖问题,也不强求立刻处理。
- 优先捕获具体异常:用
catch (IOException e),而不是catch (Exception e) - 多个异常类型用多 catch(Java 7+):
catch (IOException | SQLException e) - 不要为了“整洁”把所有异常塞进一个
catch块——不同异常的恢复策略往往完全不同
日志记录时别丢堆栈
即使你决定忽略某个异常(极少数情况),也绝不能只记 e.getMessage()。没有堆栈,根本定位不到哪行代码、哪个线程、什么上下文出的问题。
容易踩的坑:用 log.warn("Failed to parse: " + e.getMessage()),结果线上报错只看到 “null”,完全没法查。
- 正确写法:
log.warn("Failed to parse config", e)(把异常对象当参数传) - 如果用 SLF4J,确保 logger 方法签名支持 Throwable 参数,否则堆栈会被丢弃
- 不要在 catch 块里只写
e.printStackTrace()—— 它输出到 stderr,通常不进日志系统,线上等于没留痕
catch 时停下来问一句:这个异常,我此刻有责任、也有能力处理吗?








