当前位置: 首页 > news >正文

从 JDK 1.8 切换到 JDK 21 时遇到 NoProviderFoundException 该如何解决?

网罗开发(小红书、快手、视频号同名)

  大家好,我是 展菲,目前在上市企业从事人工智能项目研发管理工作,平时热衷于分享各种编程领域的软硬技能知识以及前沿技术,包括iOS、前端、Harmony OS、Java、Python等方向。在移动端开发、鸿蒙开发、物联网、嵌入式、云原生、开源等领域有深厚造诣。

图书作者:《ESP32-C3 物联网工程开发实战》
图书作者:《SwiftUI 入门,进阶与实战》
超级个体:COC上海社区主理人
特约讲师:大学讲师,谷歌亚马逊分享嘉宾
科技博主:华为HDE/HDG

我的博客内容涵盖广泛,主要分享技术教程、Bug解决方案、开发工具使用、前沿科技资讯、产品评测与使用体验。我特别关注云服务产品评测、AI 产品对比、开发板性能测试以及技术报告,同时也会提供产品优缺点分析、横向对比,并分享技术沙龙与行业大会的参会体验。我的目标是为读者提供有深度、有实用价值的技术洞察与分析。

展菲:您的前沿技术领航员
👋 大家好,我是展菲!
📱 全网搜索“展菲”,即可纵览我在各大平台的知识足迹。
📣 公众号“Swift社区”,每周定时推送干货满满的技术长文,从新兴框架的剖析到运维实战的复盘,助您技术进阶之路畅通无阻。
💬 微信端添加好友“fzhanfei”,与我直接交流,不管是项目瓶颈的求助,还是行业趋势的探讨,随时畅所欲言。
📅 最新动态:2025 年 3 月 17 日
快来加入技术社区,一起挖掘技术的无限潜能,携手迈向数字化新征程!


文章目录

    • 摘要
    • 为什么会出现这个错误(核心原因汇总)
    • 快速诊断步骤(从易到难)
    • 常见场景与对应解决策略
      • 普通 Java 应用(非 Spring),但运行时加载到旧版 HV
      • Spring Boot 项目
      • 部署到 App Server(如 WildFly / Tomcat / WebLogic)
      • 模块化运行(JPMS / module-path)
    • 可运行 Demo(Maven 项目)
      • 1) `pom.xml`
      • 2) `src/main/java/com/example/DemoValidator.java`
      • 3) 运行步骤(建议在干净的环境)
    • 常用排查清单(Checklist)
    • 现实中常见原因(结合你描述)
    • 总结

摘要

你把 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/模块路径把旧实现“带进来”了。下面逐条讲清楚怎么诊断和解决。

为什么会出现这个错误(核心原因汇总)

  1. 缺少匹配的 Bean Validation provider
    jakarta.validation 是 API(接口/服务),需要一个 provider(实现),如 Hibernate Validator。若没有 provider,Validation.buildDefaultValidatorFactory() 会抛 NoProviderFoundException

  2. API 与 Provider 版本不匹配
    Hibernate Validator 有多个主线版本:6.x(对应旧的 Jakarta/Javax 生态)与 8.x(面向 Jakarta Validation 3.x,即 jakarta.* 包)。如果你同时有 jakarta.validation-api 的某个版本与 provider 的期望不一致,ServiceLoader 找不到或 provider 无法注册。

  3. 类路径上存在多个不同版本的 Hibernate Validator(冲突)
    例如:你在 POM 中加了 8.0.0,但运行时另一个 JAR(或应用服务器全局库、旧的 starter、fat-jar 的打包残留)仍包含 6.2.5,最终 JVM 加载了旧版实现(导致 Version 读取到 6.2.5 的 Manifest 信息)。

  4. 运行在容器/应用服务器时,服务器自带旧实现
    像 WildFly、某些 app-server 或平台有时会把验证实现放在服务器库里,部署时会优先使用容器库,覆盖应用中的版本。

  5. 模块化(JPMS)或 module path 导致 ServiceLoader 行为不同
    当把 JAR 放到 module path 而不是 classpath 时,ServiceLoader 的查找机制和模块描述符会影响 META-INF/services 的发现;若未正确 provides/uses,可能导致 provider 未被找到。

快速诊断步骤(从易到难)

  1. 先看堆栈异常与日志
    NoProviderFoundException 说明 provider 没找到。日志里如果同时出现 HV000001: Hibernate Validator 6.2.5.Final(或其他旧版本),说明旧实现被加载了。

  2. 打印 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(路径),非常有用。

  3. Maven 层面检查(本地构建)
    在项目根目录运行:

    mvn dependency:tree -Dincludes=org.hibernate.validator
    

    或者更全面地:

    mvn dependency:tree
    

    看看是不是有多个依赖把不同版本的 hibernate-validator 引进来,或是某个 starter/pom 管理了不同版本。

  4. 检查打包产物

    • 若你生成了可执行 fat-jar(spring-boot:repackage),解压 jar(jar tf app.jar)查看 BOOT-INF/lib 中是否存在旧的 hv.jar。
    • 若是 WAR 部署到容器,检查 WEB-INF/lib 与容器共享的库目录(如 Tomcat 的 lib)是否包含旧的 hv 或 validation-api。
  5. 检查 META-INF/services 文件
    打开运行时加载的 hibernate-validator jar,确认 META-INF/services/jakarta.validation.spi.ValidationProvider(对于 jakarta API)或 javax.validation.spi.ValidationProvider(对于旧 API)存在,并且指向正确的实现类。若存在多个这种文件,可能导致冲突。

  6. 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-validatorvalidation-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 {@NotNullprivate String name;public Person(String name) { this.name = name; }}public static void main(String[] args) {// 打印 hibernate validator 版本和所在 jartry {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);}// 尝试构建 ValidatorFactorytry (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.FinalNoProviderFoundException,说明你的运行时 classpath 中存在旧版 JAR 或缺少 provider,按下面的排查清单继续查。

常用排查清单(Checklist)

逐项检查并修复:

  1. 运行时实际加载的版本:用 Version.class.getProtectionDomain().getCodeSource().getLocation() 查看具体 jar 文件位置。
  2. Maven 依赖树mvn dependency:tree 找到谁把旧版带进来,使用 <exclusions> 排除。
  3. 容器/服务器库:检查是否是服务器自带旧版,必要时在服务器中移除或在部署配置中优先使用应用库。
  4. 确保 API 与 provider 匹配:如果使用 jakarta.validation-api 3.x,就用 hibernate-validator 8.x;如果仍使用 javax.validation(老 API),用 HV 6.x。
  5. 清理并强制刷新本地仓库mvn -U clean package,必要时 mvn dependency:purge-local-repository 清缓存后再构建。
  6. 查看 META-INF/services 文件:在最终打包的运行时 JAR 中,查看是否存在 META-INF/services/jakarta.validation.spi.ValidationProvider,并确认文件内容指向正确的 provider 类。
  7. 模块化(如果使用 module-path):尝试用 classpath 运行以排除 JPMS 的影响,或者为模块加上 uses/provides
  8. 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)。按这个顺序走,能最快定位并解决问题。

http://www.xdnf.cn/news/1478197.html

相关文章:

  • 【科研成果速递-IJGIS】如何描述与分类移动对象的时空模式?一个新的分类框架与体系!
  • JDBC操作数据库所需要的组件
  • 《Kubernetes 构建 MySQL MGR 集群实战教程》
  • 使用Spring Boot DevTools快速重启功能
  • Python爬虫实战:研究Event Handling机制,构建在线教育平台的课程数据采集和分析系统
  • 使用 YAML 自动化 Azure DevOps 管道
  • browser-use 的三种启动方式详解
  • Android Framework智能座舱面试题
  • 【Python自动化】 21.2 Pandas 读取 Excel 时的 dtype 参数完全指南
  • 贪心算法应用:DNA自组装问题详解
  • Flask论坛与个人中心页面开发教程完整详细版
  • 【LeetCode 热题 100】49. 字母异位词分组
  • Windows 11 手动下载安装配置 uv、配置国内源
  • 固定资产管理系统(vue+Springboot+mybatis)
  • 行为式验证码技术解析:滑块拼图、语序选词与智能无感知
  • Vllm-0.10.1:vllm bench serve参数说明
  • 【完整源码+数据集+部署教程】农作物病害检测系统源码和数据集:改进yolo11-HSFPN
  • Flutter常用库集锦
  • Webpack热更新(HMR)底层原理详解
  • 基于定制开发开源AI智能名片S2B2C商城小程序的DMP平台离线文件上传功能优化研究
  • RK3568 Trust
  • 进程间通信(IPC)方式
  • AgentScope 1.0深度解析:技术架构、使用教程与多智能体开发实践
  • 跟着开题报告学答辩!《 Access学情分析系统的设计与实现》开题答辩实录分享!
  • Linux系统编程守护进程(36)
  • Linux笔记---TCP套接字编程
  • Docker学习笔记-网络类型
  • 【干货推荐】AI助理前端UI组件-悬浮球组件
  • 下载数据集用于图像分类并自动分为训练集和测试集方法
  • Python零基础速成指南:12周从小白到项目实战