严禁空catch块、捕获泛型异常及finally抛异常;须记录完整堆栈、捕获具体异常类型、包装异常时传入cause、用try-with-resources或addSuppressed避免异常覆盖。

空 catch 块直接吞掉异常
这是最典型的吞异常行为:catch 块里什么也不做,甚至只写个 System.out.println("error") 而不打印堆栈。异常信息一旦丢失,线上出问题时根本无法定位根因。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 绝不允许出现空
catch块;必须至少记录完整堆栈:logger.error("xxx failed", e) - 如果确定要“忽略”某个已知异常(如
InterruptedException在清理线程时),也要显式注释说明原因,并调用Thread.currentThread().interrupt()恢复中断状态 - 避免用
e.printStackTrace()—— 它输出到System.err,不受日志框架管控,容易遗漏或混乱
catch Exception 或 Throwable 掩盖具体问题
用 catch (Exception e) 会捕获所有检查/非检查异常,包括 NullPointerException、OutOfMemoryError 等本不该被业务逻辑处理的严重问题,导致错误被静默掩盖或错误恢复。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 只捕获你明确知道如何处理的**具体异常类型**,比如
IOException、SQLException - 避免捕获
Throwable,它连OutOfMemoryError都会抓,而这类错误通常不应被捕获或尝试恢复 - 若需统一兜底,应在顶层(如 Spring 的
@ControllerAdvice)做,而不是在每层业务代码里重复写catch (Exception e)
异常转换时不保留原始 cause
用 new RuntimeException("msg") 包装异常却没传入原异常作为 cause,会导致调用链断裂,堆栈里看不到最初出错位置。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 所有自定义异常包装都必须使用带
cause的构造函数:throw new ServiceException("DB query failed", e) - 检查所用框架是否自动保留 cause(如 Spring 的
DataAccessException会包装但保留原异常),不要假设它做了 - 日志中打印异常时,确保用
logger.error(msg, e)形式,而非logger.error(msg + e)—— 后者只打 toString(),丢掉堆栈
finally 中抛出异常覆盖原有异常
当 try 块已抛异常,而 finally 块又抛出另一个异常时,原始异常会被完全丢弃,只留下 finally 的异常 —— 这是 JVM 规范行为,极易误导排查方向。
实操建议:
立即学习“Java免费学习笔记(深入)”;
-
finally块中禁止抛出受检异常;对可能失败的操作(如close()),应捕获并记录,但不抛出:try { resource.close(); } catch (IOException e) { logger.warn("close failed", e); } - JDK 7+ 优先使用 try-with-resources,它自动处理资源关闭且不会掩盖
try块中的异常 - 若必须手动关闭且担心异常覆盖,可用
addSuppressed()显式附加被抑制的异常(仅限 JDK 7+)
try (BufferedReader br = new BufferedReader(new FileReader("a.txt"))) {
return br.readLine();
} catch (IOException e) {
throw new ServiceException("read failed", e);
}
真正难防的是多层包装后 cause 被意外截断,或者日志配置把 stack trace 给截短了——这些地方不报错,但会让问题变得无迹可寻。









