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


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

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

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

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

展菲:您的前沿技术领航员

👋 大家好,我是展菲!

📱 全网搜索"展菲",即可纵览我在各大平台的知识足迹。

📣 公众号"Swift社区",每周定时推送干货满满的技术长文,从新兴框架的剖析到运维实战的复盘,助您技术进阶之路畅通无阻。

💬 微信端添加好友"fzhanfei",与我直接交流,不管是项目瓶颈的求助,还是行业趋势的探讨,随时畅所欲言。

📅 最新动态:2025 年 3 月 17 日

快来加入技术社区,一起挖掘技术的无限潜能,携手迈向数字化新征程!

文章目录

摘要

你把 JDK 从 1.8 升到 21 后,运行时出现:

txt 复制代码
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 类:

    java 复制代码
    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 层面检查(本地构建)

    在项目根目录运行:

    bash 复制代码
    mvn dependency:tree -Dincludes=org.hibernate.validator

    或者更全面地:

    bash 复制代码
    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)。

  • 清理本地仓库后重新构建:

    bash 复制代码
    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

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

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) 运行步骤(建议在干净的环境)

bash 复制代码
# 构建
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)。按这个顺序走,能最快定位并解决问题。

相关推荐
0wioiw04 小时前
Go基础(④指针)
开发语言·后端·golang
DKPT4 小时前
JVM中如何调优新生代和老生代?
java·jvm·笔记·学习·spring
phltxy4 小时前
JVM——Java虚拟机学习
java·jvm·学习
How_doyou_do5 小时前
数据传输优化-异步不阻塞处理增强首屏体验
开发语言·前端·javascript
jingfeng5145 小时前
C++11可变参数模板、emplace系列接口、包装器
开发语言·c++
云天徽上5 小时前
【数据可视化-107】2025年1-7月全国出口总额Top 10省市数据分析:用Python和Pyecharts打造炫酷可视化大屏
开发语言·python·信息可视化·数据挖掘·数据分析·pyecharts
Tina表姐5 小时前
(C题|NIPT 的时点选择与胎儿的异常判定)2025年高教杯全国大学生数学建模国赛解题思路|完整代码论文集合
c语言·开发语言·数学建模
seabirdssss6 小时前
使用Spring Boot DevTools快速重启功能
java·spring boot·后端