如何让Java11的javax.validation通用库适配Java17 Spring Boot3服务?
问题:Spring 3.2(Java 17)无法兼容common-lib中的javax.validation自定义校验注解
项目环境
- common-lib:Java 11开发,无Spring依赖,包含共享类、错误码、模型、DTO及自定义校验注解
- x服务:Java 11 + Spring 2.6,可正常使用common-lib所有功能
- y服务:Java 17 + Spring 3.2,仅能使用common-lib中的模型、错误码等类,无法识别其中的自定义校验注解
问题核心
y服务基于Spring 3.2,依赖jakarta.validation体系,而common-lib使用的是旧版javax.validation注解。由于Java EE迁移到Jakarta EE时包名从javax改为jakarta,导致字节层面不兼容,y服务无法解析common-lib中的自定义校验注解。
自定义校验注解代码
@Target({TYPE}) @Retention(RUNTIME) @Constraint(validatedBy = IsValidSms.SmsValidator.class) public @interface IsValidSms { String message() default "INVALID_REQUEST_BODY"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; class SmsValidator implements ConstraintValidator<IsValidSms, SmsRequest> { public boolean isValid(SmsRequest smsRequest, ConstraintValidatorContext constraintContext) { final String smsType = smsRequest.getSmsType(); if (Objects.isNull(smsType) || smsType.isBlank()) { return Objects.nonNull(smsRequest.getContent()); } return isValidPlaceholders(smsRequest.getPlaceholders()); } private boolean isValidPlaceholders(Map<String, String> placeholders) { if (Objects.isNull(placeholders) || placeholders.isEmpty()) { return true; } return placeholders.keySet().stream().allMatch(key -> key.startsWith("{{") && key.endsWith("}}")); } } }
common-lib依赖配置
dependencies { testImplementation 'org.mockito:mockito-core:3.5.9' testImplementation 'org.mockito:mockito-junit-jupiter:3.5.9' compileOnly 'org.projectlombok:lombok' implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.11.3' implementation group: 'org.hibernate.validator', name: 'hibernate-validator', version: '6.1.6.Final' implementation 'commons-validator:commons-validator:1.7' testImplementation group: 'org.glassfish', name: 'javax.el', version: '3.0.0' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.junit.jupiter:junit-jupiter-api:5.5.2' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.5.2' testRuntimeOnly 'org.junit.vintage:junit-vintage-engine:5.5.2' implementation 'org.springframework:spring-beans:5.3.20' }
已尝试无效方案
- 给y服务添加
jakarta.validation依赖 - 给y服务手动添加
javax.validation依赖
解决方案(无需修改common-lib)
方案1:Spring原生桥接适配
直接在y服务中添加Spring提供的校验适配依赖,自动兼容javax.validation注解:
// Gradle配置 implementation 'org.springframework.boot:spring-boot-starter-validation' // 引入javax.validation API用于兼容旧注解 implementation 'javax.validation:validation-api:2.0.1.Final' // 使用兼容jakarta的Hibernate Validator版本 runtimeOnly 'org.hibernate.validator:hibernate-validator:7.0.4.Final' // 核心:Spring的validation模块负责适配javax与jakarta implementation 'org.springframework.validation:spring-validation'
如果是Maven项目:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> <dependency> <groupId>javax.validation</groupId> <artifactId>validation-api</artifactId> <version>2.0.1.Final</version> </dependency> <dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId> <version>7.0.4.Final</version> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-validation</artifactId> </dependency>
Spring的spring-validation模块会自动识别javax.validation注解,并适配到jakarta.validation的处理流程中,无需额外配置。
方案2:字节码自动转换
使用OpenRewrite插件在构建时自动修改common-lib中的字节码,将javax.validation替换为jakarta.validation:
// Gradle配置 plugins { id 'org.openrewrite.rewrite' version '5.40.0' } rewrite { // 启用javax到jakarta的迁移规则 activeRecipe('org.openrewrite.java.spring.JavaxMigrationToJakarta') } dependencies { rewrite 'org.openrewrite.recipe:rewrite-spring:5.40.0' }
执行构建时,插件会自动扫描所有依赖包,将其中的javax.validation相关类、注解替换为jakarta.validation的对应内容,从字节层面解决兼容问题。
方案3:本地注解覆盖
在y服务中创建与common-lib完全同名同路径的自定义校验注解,但使用jakarta.validation的API:
// 包路径必须与common-lib中IsValidSms完全一致 package com.your.common.package; import jakarta.validation.Constraint; import jakarta.validation.Payload; import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidatorContext; import java.lang.annotation.Target; import java.lang.annotation.Retention; import java.util.Map; import java.util.Objects; import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; @Target({TYPE}) @Retention(RUNTIME) @Constraint(validatedBy = IsValidSms.SmsValidator.class) public @interface IsValidSms { String message() default "INVALID_REQUEST_BODY"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; class SmsValidator implements ConstraintValidator<IsValidSms, SmsRequest> { // 完全复制common-lib中SmsValidator的校验逻辑 public boolean isValid(SmsRequest smsRequest, ConstraintValidatorContext constraintContext) { final String smsType = smsRequest.getSmsType(); if (Objects.isNull(smsType) || smsType.isBlank()) { return Objects.nonNull(smsRequest.getContent()); } return isValidPlaceholders(smsRequest.getPlaceholders()); } private boolean isValidPlaceholders(Map<String, String> placeholders) { if (Objects.isNull(placeholders) || placeholders.isEmpty()) { return true; } return placeholders.keySet().stream().allMatch(key -> key.startsWith("{{") && key.endsWith("}}")); } } }
利用类加载的优先级,y服务会优先加载本地的IsValidSms注解,从而适配jakarta.validation体系。这种方式适合单个注解的适配,但需要保持与common-lib的校验逻辑同步。
优先级推荐
- 优先使用方案1,Spring原生适配,配置简单且无需额外维护
- 若有大量javax依赖需要迁移,选择方案2,一次性解决所有兼容问题
- 仅单个或少数注解兼容问题时,可使用方案3,快速临时解决
内容的提问来源于stack exchange,提问作者Mert Karaman
相关产品推荐
相关产品推荐

