Java异常处理需规范化:按语义分业务、系统、参数异常;各层分层捕获与响应;自定义非受检异常用于业务中断,受检异常用于必须显式处理的外部故障;Controller用@ExceptionHandler集中处理;异常消息要“说人话”并附上下文;日志记录需结构化、脱敏、不生吞;善用try-with-resources和Optional减少异常源头。

Java异常处理不是写个try-catch就完事,关键在于让异常成为可读、可定位、可响应的信号。规范化的异常使用能大幅降低排查成本,让团队协作更顺畅。
统一异常分类与分层捕获
避免所有异常都用Exception兜底。按语义明确划分:业务异常(如OrderAlreadyPaidException)、系统异常(如DatabaseConnectionException)、参数异常(如InvalidParameterException)。各层只处理本层能响应的异常——Controller层转成HTTP状态码和友好的错误提示,Service层抛出带上下文的业务异常,DAO层将JDBC异常封装为受检或非受检的领域异常。
- 自定义异常继承
RuntimeException(非受检)用于业务逻辑中断,避免强制try-catch污染调用链 - 对必须显式处理的外部故障(如文件不存在、远程服务超时),用受检异常并配以清晰的
throws声明 - Controller中用
@ExceptionHandler集中处理,避免每个接口重复写catch
异常信息要“说人话”,附带关键上下文
不写throw new RuntimeException("error")。异常消息应说明“什么错了、在哪错的、可能为什么错”。日志中记录异常时,补全请求ID、用户ID、关键参数值等追踪线索。
- 构造异常时传入格式化字符串和变量,例如:
new InsufficientBalanceException("余额不足,当前:%s,需:%s", balance, amount) - 在日志中打印异常堆栈前,先输出结构化上下文:
log.error("支付失败[orderId:{}][userId:{}]", orderId, userId, e) - 对外返回的错误信息脱敏,不暴露类名、路径、数据库字段等敏感细节
禁止“生吞”异常和空catch块
空catch是维护黑洞。即使认为异常可忽略,也必须记录日志并说明理由,否则问题会在深夜报警时突然爆发。
立即学习“Java免费学习笔记(深入)”;
- 所有
catch块至少调用log.warn(..., e)或log.error(..., e) - 不要用
e.printStackTrace()——它不走日志框架,无法配置级别和输出目标 - 若真需忽略(如关闭资源时的二次异常),注释清楚原因,例如:
// 忽略关闭流时的IOException,主异常已记录
善用try-with-resources和Optional减少异常源头
很多异常源于资源未释放或空指针。用现代语法从根源上压低异常发生概率,比层层catch更治本。
- 所有实现
AutoCloseable的资源(文件、连接、流)必须用try-with-resources自动管理 - 方法返回可能为空的对象时,优先返回
Optional,调用方用ifPresent或orElseThrow显式处理空值场景 - 参数校验前置:用
Objects.requireNonNull或Validate.notNull快速失败,避免深层调用后才抛NPE
基本上就这些。异常不是bug的遮羞布,而是系统健康状况的仪表盘。写得清楚,查得明白,改得安心。










