You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SpringBoot 3.2.0升级后Swagger接口实现触发验证约束异常求助

解决SpringBoot 3.2.0中方法重写的Jakarta Validation约束冲突问题

问题场景

项目升级至SpringBoot 3.2.0,包含两个模块:

  • commons-api模块通过OpenAPI生成器从YAML文件生成Swagger接口与DTO,依赖及插件配置见下文。
  • api-nomenclature模块实现生成的MenuApi接口时,执行GET请求触发jakarta.validation.ConstraintDeclarationException:

jakarta.validation.ConstraintDeclarationException: HV000151: 重写方法不得重新定义参数约束配置,但方法MenuController#getAllMenus(Optional, Optional, Optional, Optional, Optional)重新定义了MenuApi#getAllMenus(Optional, Optional, Optional, Optional, Optional)的配置。

相关代码与配置

commons-api模块依赖

<dependencies>
    <dependency>
        <groupId>org.springdoc</groupId>
        <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
        <version>${springdoc-openapi-webmvc-ui.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
    <dependency>
        <groupId>org.openapitools</groupId>
        <artifactId>jackson-databind-nullable</artifactId>
        <version>${jackson-databind-nullable.version}</version>
    </dependency>
    <dependency>
        <groupId>jakarta.servlet</groupId>
        <artifactId>jakarta.servlet-api</artifactId>
    </dependency>
</dependencies>

OpenAPI生成器插件配置

<plugin>
   <groupId>org.openapitools</groupId>
   <artifactId>openapi-generator-maven-plugin</artifactId>
   <version>${openapitools.version}</version>
   <executions>
      <execution>
         <id>contrat-nomenclature</id>
         <phase>generate-sources</phase>
         <goals>
           <goal>generate</goal>
         </goals>
         <configuration>
         <inputSpec>${project.basedir}/src/main/resources/contratnomenclature.yaml</inputSpec>
         <generatorName>spring</generatorName>
         <configOptions>
           <sourceFolder>main/java</sourceFolder>
           <interfaceOnly>true</interfaceOnly>
           <skipDefaultInterface>true</skipDefaultInterface>
           <hideGenerationTimestamp>true</hideGenerationTimestamp>
           <dateLibrary>java</dateLibrary>
           <useTags>true</useTags>
           <useOptional>true</useOptional>
           <useJakartaEe>true</useJakartaEe>
           <useSpringBoot3>true</useSpringBoot3>
       </configOptions>
               
      
     <modelPackage>${project.groupId}....model</modelPackage>
     <apiPackage>${project.groupId}....api</apiPackage>
       ...
     </configuration>
   </execution>
 </executions>
</plugin> 

接口实现代码(问题代码)

@CrossOrigin
@RestController
public class MenuController implements MenuApi {
    @Override
    public ResponseEntity<List<Menu>> getAllMenus(Optional<String> q, Optional<Integer> page, Optional<@Min(1) @Max(50) Integer> size, Optional<Boolean> full, Optional<String> sort) {
        return new ResponseEntity<>(menuService.getAllMenus(q, page, size, full, sort), HttpStatus.OK);
    }
}

生成的MenuApi接口方法

@Operation(
    operationId = "getAllMenus",
    summary = "Retrieve all menus",
    description = "Retrieve all menus",
    tags = { "Menu" },
    responses = {
        @ApiResponse(responseCode = "200", description = "Successful operation", content = {
            @Content(mediaType = "application/json", array = @ArraySchema(schema = @Schema(implementation = Menu.class)))
        }),
        @ApiResponse(responseCode = "400", description = "Invalid status value"),
        @ApiResponse(responseCode = "404", description = "Menu not found")
    }
)
@RequestMapping(
    method = RequestMethod.GET,
    value = "/nomenclature/menu",
    produces = { "application/json" }
)
    ResponseEntity<List<Menu>> getAllMenus(
        @Parameter(name = "q", description = "Terme pour filter sur le nom", in = ParameterIn.QUERY) @Valid @RequestParam(value = "q", required = false) Optional<String> q,
        @Parameter(name = "page", description = "Numéro de la page", in = ParameterIn.QUERY) @Valid @RequestParam(value = "page", required = false) Optional<Integer> page,
        @Parameter(name = "size", description = "Nombre d'éléments par page", in = ParameterIn.QUERY) @Valid @RequestParam(value = "size", required = false, defaultValue = "20") Optional<@Min(1) @Max(50) Integer> size,
        @Parameter(name = "full", description = "Retourne la propriete et les sous éléments", in = ParameterIn.QUERY) @Valid @RequestParam(value = "full", required = false) Optional<Boolean> full,
        @Parameter(name = "sort", description = "Tri", in = ParameterIn.QUERY) @Valid @RequestParam(value = "sort", required = false) Optional<String> sort
    );

原因分析

Jakarta Validation(Hibernate Validator作为实现)的规则明确:子类重写父类/接口方法时,不能重新定义参数的约束配置。这里生成的MenuApi接口中,size参数已经带有@Min(1)和@Max(50)约束,而实现类MenuController的重写方法中再次添加了这两个注解,导致约束重复定义,触发异常。

解决方案

方案1:移除实现类方法参数的重复约束

直接删除实现类中size参数上的@Min和@Max注解,接口已定义的约束会自动生效:

@CrossOrigin
@RestController
public class MenuController implements MenuApi {
    @Override
    public ResponseEntity<List<Menu>> getAllMenus(Optional<String> q, Optional<Integer> page, Optional<Integer> size, Optional<Boolean> full, Optional<String> sort) {
        return new ResponseEntity<>(menuService.getAllMenus(q, page, size, full, sort), HttpStatus.OK);
    }
}

方案2:调整OpenAPI生成器配置(可选)

如果希望生成的接口不带参数约束,或者需要自定义约束逻辑,可以修改生成器的configOptions,添加如下配置:

<configOptions>
    <!-- 其他已有配置 -->
    <useBeanValidation>false</useBeanValidation>
</configOptions>

此配置会禁止生成器在接口参数上添加Bean Validation注解,之后可以在实现类中按需添加约束。

方案3:使用Validation分组(复杂场景)

如果需要在子类中调整约束逻辑,可以通过Jakarta Validation的分组机制实现:

  1. 定义分组接口:
public interface UpdateGroup {}
  1. 修改生成的接口(或调整YAML文件),给约束指定分组:
Optional<@Min(value = 1, groups = UpdateGroup.class) @Max(value = 50, groups = UpdateGroup.class) Integer> size
  1. 在实现类方法上指定生效的分组:
@Override
@Validated(UpdateGroup.class)
public ResponseEntity<List<Menu>> getAllMenus(Optional<String> q, Optional<Integer> page, Optional<@Min(1) @Max(50) Integer> size, Optional<Boolean> full, Optional<String> sort) {
    // 业务逻辑
}

注意:此方案需要确保生成器支持分组配置,可能需要调整OpenAPI YAML文件中的约束定义。


内容的提问来源于stack exchange,提问作者asmiou

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 23:14:52