永远别用空 catch,必须记录日志、重新抛出、设置fallback或执行补偿逻辑;仅log.error()不算处理,需确保调用方感知失败;优先捕获具体异常类型,禁用catch(Exception e)和@SneakyThrows关键路径。

空 catch 会掩盖真实问题
Java 里写 catch (Exception e) {} 或 catch (Throwable t) {},表面看程序不崩,实际等于把报警器关了。异常没被记录、没被传播、也没触发任何恢复逻辑,下次出问题时你连“哪行代码、什么条件下、抛了什么错”都查不到。
常见错误现象:
• 接口返回空数据或默认值,但日志里一片空白
• 同一段代码在测试环境正常,上线后偶发失败且无法复现
• NullPointerException 被吞掉,后续调用基于 null 对象继续执行,报出更诡异的 IllegalStateException
- 永远别用空
catch,哪怕只是临时调试——IDE 的“suppress warning”不是免责金牌 - 如果真确定该异常可忽略(极少见),至少加注释说明原因,比如:
// ignore: 文件可能不存在,属于预期行为 - 所有
catch块必须包含至少一项动作:记录日志、重新抛出、设置 fallback 值、或调用明确的补偿逻辑
log.error() 不等于“已处理”
只写 log.error("something went wrong", e) 然后结束 catch,是半吊子处理。日志写了,但调用方完全不知道失败了,上游可能继续走流程,导致状态不一致。
使用场景举例:
• HTTP 接口里捕获数据库异常,只打日志却不返回 500,前端会收一个 200 + 空响应
• 异步任务中吞掉 InterruptedException,线程中断信号丢失,任务无法被优雅停止
立即学习“Java免费学习笔记(深入)”;
- 区分“记录”和“处理”:记录是留痕,处理是改变控制流或状态
- 对受检异常(如
IOException),若业务上无法恢复,应包装为运行时异常(如RuntimeException)向上抛,而不是静默吃掉 - 对非受检异常(如
IllegalArgumentException),更要警惕——它往往暴露的是参数校验缺失或前置逻辑缺陷,不该进catch
try-with-resources 不能替代异常处理逻辑
有人以为用了 try (FileInputStream fis = new FileInputStream(...)) { ... } 就算安全了,结果在 try 块里又套了个空 catch。资源自动关闭只解决泄漏,不解决业务异常语义。
性能 / 兼容性影响:
• 空 catch 不影响 JVM 性能,但会让监控系统收不到异常指标,告警失效
• 在 JDK 17+ 的 sealed class 或 pattern matching 场景下,吞掉异常可能绕过编译器强制的 exhaustive handling 检查
-
try-with-resources只保证close()执行,不保证close()本身不出异常(比如磁盘满导致close失败) - 若需在
close()后做清理,别依赖finally里的空catch,改用try-catch显式包裹close()并处理其异常 - 多资源声明时,第一个资源的
close()异常会被保留,后续资源的close()异常会作为 suppressed exception 附加——空catch会让这些都被丢弃
哪些情况看似合理、实则危险
有些写法看起来有道理,但落地时极易失控。比如“只 catch 特定异常”,结果类型写错了;或者“只在测试环境空 catch”,结果配置漏切。
常见错误现象:
• catch (SQLException e) 写成 catch (SQLExcepton e)(拼错类名),实际捕获的是 Exception,变成隐式宽泛捕获
• 使用 Lombok 的 @SneakyThrows,底层仍是空 catch 包装,掩盖了异常传播路径
• 在循环里对每个元素单独 try-catch,却没统计失败数量或标记失败项,导致批量处理结果不可信
- 避免用通配符捕获:禁用
catch (Exception e),优先捕获具体子类,如catch (JsonProcessingException e) - 禁用
@SneakyThrows处理业务关键路径的异常——它适合工具方法,不适合服务入口或数据转换层 - 批量操作中,宁可用
List<Result>返回每个子项状态,也不要靠空catch让整个批次“假装成功”
最麻烦的不是写错 catch,而是写完就忘了它存在。几个月后谁还记得那个 catch (TimeoutException e) {} 其实本该触发降级?异常处理不是代码补丁,是契约的一部分——你吞掉的每一个异常,都在悄悄修改这个契约。








