Hibernate Validator 9.1.6.Final(2026-11-01,同日还有 9.0.3.Final)修了 HV-2283:Jakarta EL 6 本该支持 record 属性,但消息插值上下文没挂上 RecordELResolver。类级约束消息里写 ${validatedValue.start} 这类组件访问,9.1.5 只会 WARN HV000129,消息原样留着 EL;升到 9.1.6 后同一模板能拼出真实值。Spring Boot 4.1.2 默认仍是 9.1.5,需要显式抬版本才能带上修复。

许可证 Apache-2.0。仓库 hibernate/hibernate-validator。主源见 9.1.6.Final、9.1 发布页,以及 Jira HV-2283。

场景

请求体改成 Java record 很常见。交叉字段校验会挂在类型上,消息模板想直接引用组件,例如「区间非法: start=…, end=…」。字段级约束的 validatedValue 是字段本身;类级约束的 validatedValue 才是整个 record。

EL 6 有标准 RecordELResolver。9.1.5 的消息插值只挂了 List / Map / ResourceBundle / Bean 一类 resolver,没挂 record。访问 start、end 时走 BeanELResolver,按 JavaBean 找 getter,record 组件对不上,就报 PropertyNotFoundException。实现里异常被吃掉,对外只剩 WARN 和未插值的模板原文。业务校验「成功」返回了 violation,前端或日志却看到 ${validatedValue.start} 这种半成品。

9.1.6 增加了只解析 record 组件的 RecordPropertiesELResolver(限制在组件访问器,不把任意无参方法当属性暴露),并注册进消息插值上下文。9.0 线对应修在 9.0.3.Final。

Boot 4.1.2 双版本对照

同一台机器、JDK 21.0.12.1、Spring Boot 4.1.2(BOM 自带 hibernate-validator 9.1.5.Final)。两个独立进程,只改 hibernate-validator.version,其余代码相同:一个类级约束 + 一个 record。

@CrossField
public record RangeRequest(int start, int end) {
}

@Constraint(validatedBy = CrossFieldValidator.class)
public @interface CrossField {
  String message() default "区间非法: start=${validatedValue.start}, end=${validatedValue.end}";
  // groups / payload 省略
}

public class CrossFieldValidator implements ConstraintValidator<CrossField, RangeRequest> {
  @Override
  public boolean isValid(RangeRequest value, ConstraintValidatorContext context) {
    return value == null || value.start() <= value.end();
  }
}

父 POM 用 Boot 4.1.2,校验 starter 照常引;对照时分别钉:

<properties>
  <hibernate-validator.version>9.1.5.Final</hibernate-validator.version>
  <!-- 或 9.1.6.Final -->
</properties>

启动后校验 new RangeRequest(9, 3)。

9.1.5(摘录):

HV_VERSION=9.1.5.Final
HV_JAR=file:/.../hibernate-validator-9.1.5.Final.jar
2026-11-02T08:18:20.467+08:00  WARN ... o.h.v.i.e.m.ElTermResolver               : HV000129: EL expression '${validatedValue.start}' references an unknown property
jakarta.el.PropertyNotFoundException: Property [start] not found on type [com.example.hvdemo.RangeRequest]
2026-11-02T08:18:20.469+08:00  WARN ... o.h.v.i.e.m.ElTermResolver               : HV000129: EL expression '${validatedValue.end}' references an unknown property
jakarta.el.PropertyNotFoundException: Property [end] not found on type [com.example.hvdemo.RangeRequest]
VALIDATE_OK count=1
MESSAGE=区间非法: start=${validatedValue.start}, end=${validatedValue.end}
TEMPLATE=区间非法: start=${validatedValue.start}, end=${validatedValue.end}

9.1.6(摘录):

HV_VERSION=9.1.6.Final
HV_JAR=file:/.../hibernate-validator-9.1.6.Final.jar
VALIDATE_OK count=1
MESSAGE=区间非法: start=9, end=3
TEMPLATE=区间非法: start=${validatedValue.start}, end=${validatedValue.end}

两边都是 1 条 violation,约束本身没变。变的是消息插值:旧版刷两条 HV000129,MESSAGE 仍是模板原文;新版无 WARN,MESSAGE 变成 区间非法: start=9, end=3。TEMPLATE 两边一致,说明模板没写错,是 EL 解析补上了。

参数校验、分组那一套用法可对照站内旧文 Hibernate Validator 校验参数全攻略;这次只盯 record 组件在类级消息里的可读性。

升级时注意

Boot 4.1.2 升校验 starter 不会自动到 9.1.6,要在 BOM 属性里改 hibernate-validator.version,或等后续 Boot 抬默认值。走 9.0 线的项目对应升 9.0.3.Final。

消息里若写的是 ${validatedValue.name()} 这种方法调用形态,默认特性级别仍不会当属性解析;需要方法级 EL 时再单独开 BEAN_METHODS。组件名访问 ${validatedValue.name} / ${validatedValue['name']} 才是这次修复覆盖的路径。

同日 Hibernate ORM 也发了 7.4.15.Final(含 HHH-20910 等),与 Validator 不是同一条线,本文未叠写。

— 感谢阅读 —
广告

一起交流

分享你的思考,让讨论更进一步。