业务校验不应使用 RuntimeException,而应采用声明式校验(如@Valid)和结构化响应(如Result.fail),以区分预期业务结果与真正系统异常,提升可维护性、可观测性和前端交互体验。

业务校验不该用 RuntimeException 驱动。
校验本质是业务逻辑,不是程序错误
手机号格式不对、用户名已存在、余额不足——这些都不是系统崩溃或代码缺陷,而是用户输入或业务状态的正常反馈。抛出 RuntimeException(比如 IllegalArgumentException 或自定义运行时异常)会混淆“意外故障”和“预期业务结果”,导致调用方难以区分真正需要告警的错误和可预期的业务拒绝。
- 日志中大量出现
RuntimeException会掩盖真实异常,干扰问题定位 - 全局异常处理器若统一返回 500,前端无法做差异化提示(如“手机号不合法” vs “服务暂时不可用”)
- Feign、Dubbo 等远程调用默认将运行时异常转为失败响应,破坏语义一致性
@Valid 和 @Validated 是更干净的替代方案
Spring 的声明式校验把规则写在实体字段或参数上,由框架自动触发并统一拦截,天然区分校验失败(MethodArgumentNotValidException 或 ConstraintViolationException)与运行时异常。
- 前端传错字段 → 返回 400 + 明确错误字段和消息,无需业务代码介入
- 校验逻辑集中管理,避免散落在 service 方法里一堆
if (xxx == null) - 配合分组(
@Validated({Create.class})),能精准控制不同接口的校验粒度
真正该用 RuntimeException 的场景很有限
仅当发生本不该出现、无法恢复、需立即中断流程的状况时才抛运行时异常:
- 数据库连接池耗尽(
IllegalStateException) - 配置项缺失导致核心功能失效(
RuntimeException包裹MissingConfigurationException) - 第三方服务返回非法结构,且无 fallback 路径
这类异常应有明确分类、带上下文信息,并由顶层统一兜底记录和告警。
推荐做法:显式校验 + 结构化响应
对业务规则判断,直接返回带 code/message/data 的标准响应体:
Result.fail("USER_EXISTS", "用户名已被注册")Result.fail(BusinessCode.INSUFFICIENT_BALANCE, "余额不足")- Controller 层只处理校验异常,不主动 throw RuntimeException
这样既清晰表达了业务意图,又便于前端解析、埋点统计和灰度策略控制。


















