从 JDK 1.8 切换到 JDK 21 时遇到 NoProviderFoundException 该如何解决?
并且你已经在 pom.xml 明确加入了 hibernate-validator:8.0.0.Final,但运行日志里却还能看到 Hibernate Validator 6.2.5.Final 的打印(即实际加载的仍是旧版本)。这说明问题不是单纯缺依赖,而是类路径(classpath)上存在版本冲突 / 旧的 provider 仍被加载,或 API 与 provider 版本不匹配,或运行时的 c
大家好,我是 展菲,目前在上市企业从事人工智能项目研发管理工作,平时热衷于分享各种编程领域的软硬技能知识以及前沿技术,包括iOS、前端、Harmony OS、Java、Python等方向。在移动端开发、鸿蒙开发、物联网、嵌入式、云原生、开源等领域有深厚造诣。
图书作者:《ESP32-C3 物联网工程开发实战》
图书作者:《SwiftUI 入门,进阶与实战》
超级个体:COC上海社区主理人
特约讲师:大学讲师,谷歌亚马逊分享嘉宾
科技博主:华为HDE/HDG
我的博客内容涵盖广泛,主要分享技术教程、Bug解决方案、开发工具使用、前沿科技资讯、产品评测与使用体验。我特别关注云服务产品评测、AI 产品对比、开发板性能测试以及技术报告,同时也会提供产品优缺点分析、横向对比,并分享技术沙龙与行业大会的参会体验。我的目标是为读者提供有深度、有实用价值的技术洞察与分析。
展菲:您的前沿技术领航员
👋 大家好,我是展菲!
📱 全网搜索“展菲”,即可纵览我在各大平台的知识足迹。
📣 公众号“Swift社区”,每周定时推送干货满满的技术长文,从新兴框架的剖析到运维实战的复盘,助您技术进阶之路畅通无阻。
💬 微信端添加好友“fzhanfei”,与我直接交流,不管是项目瓶颈的求助,还是行业趋势的探讨,随时畅所欲言。
📅 最新动态:2025 年 3 月 17 日
快来加入技术社区,一起挖掘技术的无限潜能,携手迈向数字化新征程!
文章目录
摘要
你把 JDK 从 1.8 升到 21 后,运行时出现:
javax.validation.NoProviderFoundException:
Unable to create a Configuration, because no Jakarta Bean Validation provider could be found.
Add a provider like Hibernate Validator (RI) to your classpath.
并且你已经在 pom.xml
明确加入了 hibernate-validator:8.0.0.Final
,但运行日志里却还能看到 Hibernate Validator 6.2.5.Final
的打印(即实际加载的仍是旧版本)。这说明问题不是单纯缺依赖,而是类路径(classpath)上存在版本冲突 / 旧的 provider 仍被加载,或 API 与 provider 版本不匹配,或运行时的 classloader/模块路径把旧实现“带进来”了。下面逐条讲清楚怎么诊断和解决。
为什么会出现这个错误(核心原因汇总)
-
缺少匹配的 Bean Validation provider
jakarta.validation
是 API(接口/服务),需要一个 provider(实现),如 Hibernate Validator。若没有 provider,Validation.buildDefaultValidatorFactory()
会抛NoProviderFoundException
。 -
API 与 Provider 版本不匹配
Hibernate Validator 有多个主线版本:6.x
(对应旧的 Jakarta/Javax 生态)与8.x
(面向 Jakarta Validation 3.x,即 jakarta.* 包)。如果你同时有jakarta.validation-api
的某个版本与 provider 的期望不一致,ServiceLoader 找不到或 provider 无法注册。 -
类路径上存在多个不同版本的 Hibernate Validator(冲突)
例如:你在 POM 中加了 8.0.0,但运行时另一个 JAR(或应用服务器全局库、旧的 starter、fat-jar 的打包残留)仍包含 6.2.5,最终 JVM 加载了旧版实现(导致Version
读取到 6.2.5 的 Manifest 信息)。 -
运行在容器/应用服务器时,服务器自带旧实现
像 WildFly、某些 app-server 或平台有时会把验证实现放在服务器库里,部署时会优先使用容器库,覆盖应用中的版本。 -
模块化(JPMS)或 module path 导致 ServiceLoader 行为不同
当把 JAR 放到 module path 而不是 classpath 时,ServiceLoader 的查找机制和模块描述符会影响META-INF/services
的发现;若未正确provides
/uses
,可能导致 provider 未被找到。
快速诊断步骤(从易到难)
-
先看堆栈异常与日志
NoProviderFoundException
说明 provider 没找到。日志里如果同时出现HV000001: Hibernate Validator 6.2.5.Final
(或其他旧版本),说明旧实现被加载了。 -
打印 provider 版本与来源(运行时定位)
在程序启动处加入几行调试代码,能清楚看到究竟哪个 jar 提供了Version
类:System.out.println("Loaded HV Version: " + org.hibernate.validator.internal.engine.Version.getVersionString()); System.out.println("Version.class location: " + org.hibernate.validator.internal.engine.Version.class.getProtectionDomain().getCodeSource().getLocation());
输出会告诉你
Version
来自哪个 JAR(路径),非常有用。 -
Maven 层面检查(本地构建)
在项目根目录运行:mvn dependency:tree -Dincludes=org.hibernate.validator
或者更全面地:
mvn dependency:tree
看看是不是有多个依赖把不同版本的
hibernate-validator
引进来,或是某个 starter/pom 管理了不同版本。 -
检查打包产物
- 若你生成了可执行 fat-jar(spring-boot:repackage),解压 jar(
jar tf app.jar
)查看BOOT-INF/lib
中是否存在旧的 hv.jar。 - 若是 WAR 部署到容器,检查
WEB-INF/lib
与容器共享的库目录(如 Tomcat 的lib
)是否包含旧的 hv 或 validation-api。
- 若你生成了可执行 fat-jar(spring-boot:repackage),解压 jar(
-
检查
META-INF/services
文件
打开运行时加载的 hibernate-validator jar,确认META-INF/services/jakarta.validation.spi.ValidationProvider
(对于 jakarta API)或javax.validation.spi.ValidationProvider
(对于旧 API)存在,并且指向正确的实现类。若存在多个这种文件,可能导致冲突。 -
ClassLoader / 模块路径
如果你用的是模块化运行(module-path),尝试把相关 jar 放回 classpath 运行(非 module-path)看是否正常;或为模块添加必要的provides
/uses
。通常先用 classpath 运行能最少变量地定位问题。
常见场景与对应解决策略
普通 Java 应用(非 Spring),但运行时加载到旧版 HV
原因:classpath 上存在旧版 HV JAR。
解决:
-
用
mvn dependency:tree
找到哪个依赖带入旧版,<exclusions>
排除它,或把你需要的版本放在更高优先级(dependencyManagement)。 -
清理本地仓库后重新构建:
mvn dependency:purge-local-repository -DmanualInclude="org.hibernate.validator:hibernate-validator" mvn clean package -U
-
在运行时打印
Version.class
来确认 jar 路径,删除/覆盖那份旧 jar。
Spring Boot 项目
- 如果你用 Spring Boot 2.x:它依赖的是
hibernate-validator 6.x
(基于 javax/老 API),而你却用了 hibernate-validator 8(基于 jakarta.*)。Spring Boot 2.x 与 HV8 不兼容。
方案:要么回退到hibernate-validator 6.x
(并使用javax.validation
API),要么升级 Spring Boot 到 3.x(它使用 Jakarta 命名空间并兼容 HV8)。 - 如果你用 Spring Boot 3.x:它应该与 HV8 匹配。若你仍看到 HV6,说明有其它依赖将旧版带进来(检查依赖树并排除)。
- 在 Spring Boot 中优先使用 Spring Boot 的依赖管理来对齐版本(不要手动单独升级某些关键库而忽视 Boot 版本)。
部署到 App Server(如 WildFly / Tomcat / WebLogic)
-
检查容器
lib
或共享模块有没有内置hibernate-validator
或validation-api
。如果有:- 在 server 里移除旧的实现(如果可行);或
- 在应用的
WEB-INF/lib
中使用Class-Loader
配置(Tomcat 的loader
或 WebSphere 的 classloading policy)优先使用应用库;或 - 配置容器使其不提供该实现(不同服务器操作方式不同)。
模块化运行(JPMS / module-path)
- 如果你把 jar 放在 module-path,ServiceLoader 发现机制和模块描述有关。最简单先把应用按 classpath 运行来验证问题是否与模块化有关。
- 若必须用 module-path,需要在模块声明中添加
uses jakarta.validation.spi.ValidationProvider
,并确保 provider 模块provides
相应服务(这通常不适合临时调整,除非你熟悉 JPMS)。
可运行 Demo(Maven 项目)
下面给出一个最小可运行的 Maven 项目,在 JDK21 上用,演示如何正确引入 Hibernate Validator 8 与 Jakarta Validation API,并验证 provider 能被加载。你可以把它拉到本地跑一下来验证环境。
1) pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" ...>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>hv21-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>
<dependencies>
<!-- Jakarta Validation API 3.x -->
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>3.0.2</version>
</dependency>
<!-- Hibernate Validator 8 (provider) -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>8.0.0.Final</version>
</dependency>
<!-- EL implementation required by Hibernate Validator (at runtime) -->
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>jakarta.el</artifactId>
<version>4.0.2</version>
</dependency>
<!-- logging for visibility -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.7</version>
</dependency>
</dependencies>
</project>
注意:上面使用
jakarta.validation-api:3.0.2
+hibernate-validator:8.0.0.Final
+jakarta.el:4.0.2
,这是匹配的组合(Jakarta 命名空间)。
2) src/main/java/com/example/DemoValidator.java
package com.example;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import jakarta.validation.ValidatorFactory;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.ConstraintViolation;
import java.util.Set;
public class DemoValidator {
public static class Person {
@NotNull
private String name;
public Person(String name) { this.name = name; }
}
public static void main(String[] args) {
// 打印 hibernate validator 版本和所在 jar
try {
String hvVersion = org.hibernate.validator.internal.engine.Version.getVersionString();
System.out.println("Hibernate Validator version: " + hvVersion);
System.out.println("Version.class location: " +
org.hibernate.validator.internal.engine.Version.class.getProtectionDomain()
.getCodeSource().getLocation());
} catch (Throwable t) {
System.out.println("Cannot read Version: " + t);
}
// 尝试构建 ValidatorFactory
try (ValidatorFactory factory = Validation.buildDefaultValidatorFactory()) {
Validator validator = factory.getValidator();
System.out.println("Got validator: " + validator);
Person p = new Person(null);
Set<ConstraintViolation<Person>> violations = validator.validate(p);
System.out.println("Violations count: " + violations.size());
for (ConstraintViolation<Person> v : violations) {
System.out.println(" " + v.getPropertyPath() + " -> " + v.getMessage());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
3) 运行步骤(建议在干净的环境)
# 构建
mvn -U clean package
# 运行(要求 JDK 21)
java -cp target/hv21-demo-0.0.1-SNAPSHOT.jar:target/dependency/* com.example.DemoValidator
(注:把依赖解包或用 mvn dependency:copy-dependencies
将依赖放到 target/dependency
,然后拼 classpath 运行,或使用 exec:java 插件直接运行)
期望输出(关键点):
- 打印
Hibernate Validator version: 8.0.0.Final
Validation.buildDefaultValidatorFactory()
能成功返回 factory,且violations
会显示name
为空的违规信息。
如果输出仍然显示 Hibernate Validator 6.2.5.Final
或 NoProviderFoundException
,说明你的运行时 classpath 中存在旧版 JAR 或缺少 provider,按下面的排查清单继续查。
常用排查清单(Checklist)
逐项检查并修复:
- 运行时实际加载的版本:用
Version.class.getProtectionDomain().getCodeSource().getLocation()
查看具体 jar 文件位置。 - Maven 依赖树:
mvn dependency:tree
找到谁把旧版带进来,使用<exclusions>
排除。 - 容器/服务器库:检查是否是服务器自带旧版,必要时在服务器中移除或在部署配置中优先使用应用库。
- 确保 API 与 provider 匹配:如果使用
jakarta.validation-api
3.x,就用hibernate-validator
8.x;如果仍使用javax.validation
(老 API),用 HV 6.x。 - 清理并强制刷新本地仓库:
mvn -U clean package
,必要时mvn dependency:purge-local-repository
清缓存后再构建。 - 查看 META-INF/services 文件:在最终打包的运行时 JAR 中,查看是否存在
META-INF/services/jakarta.validation.spi.ValidationProvider
,并确认文件内容指向正确的 provider 类。 - 模块化(如果使用 module-path):尝试用 classpath 运行以排除 JPMS 的影响,或者为模块加上
uses
/provides
。 - Spring Boot 特殊处理:如果使用 Spring Boot,请用 Boot 的依赖管理匹配版本(Spring Boot 3.x 对应 Jakarta/HV8)。不要手动混用 Spring Boot 2.x + HV8。
现实中常见原因(结合你描述)
你提到:明明 pom
指定了 hibernate-validator:8.0.0.Final
,但是日志中仍打印 Hibernate Validator 6.2.5.Final
—— 这几乎可以断定 运行时存在 6.2.5 的 jar,来源常见于:
- Spring Boot 依赖管理(你可能在 Spring Boot 2.x 项目里手动加了 HV8,但 Boot 管理的其他依赖仍拉 6.x);
- 应用服务器或容器自带的库(例如 WildFly/JBoss/EAP 常带老版本);
- 打包时有残留旧 jar(fat-jar 打包时可能把旧版本包含进去);
- 某个第三方库把老版本 shaded/shadow 或者以不同坐标包含。
因此请按上面的步骤从 “打印类来源” → “mvn dependency:tree” → “检查运行时 jar” 逐步定位并删除/排除旧版本。
总结
这个问题本质是 运行时类路径(classpath)不纯净或 API/实现不匹配,与 JDK21 本身没有直接“神秘问题”。先确认实际加载的 JAR 来源(Version.class
的 location),然后用 mvn dependency:tree
排查并排除旧版本,或升级应用/容器使其与 Jakarta/HV8 匹配(例如升级到 Spring Boot 3)。按这个顺序走,能最快定位并解决问题。
更多推荐
所有评论(0)