Quarkus中hibernate-validator无法校验DTO的问题排查与解决
Quarkus接口校验响应异常问题分析与解决
问题场景
基于Quarkus开发的PDF生成接口,当请求参数name为null或空值时,预期返回400状态码及校验提示,但Postman仅显示400状态码,同时提示“Failed to load PDF file.”和“The request cannot be fulfilled due to bad syntax.”。接口相关代码及依赖如下:
核心代码
import com.company.report.dto.ReportDto import com.company.report.service.ReportService import javax.enterprise.context.ApplicationScoped import javax.validation.Valid import javax.ws.rs.Consumes import javax.ws.rs.POST import javax.ws.rs.Path import javax.ws.rs.Produces import javax.ws.rs.core.MediaType import javax.ws.rs.core.Response @ApplicationScoped @Path("/app") class ReportController( private val reportService: ReportService ) { @POST @Path("/report") @Consumes(MediaType.APPLICATION_JSON) @Produces("application/pdf") fun generateReport(@Valid reportDto: ReportDto): Response { return Response .ok(reportService.generatePdfReport(reportDto), "application/pdf") .header("Content-Disposition", "inline; filename=\"Report.pdf\"") .build() } } // ReportDto import com.fasterxml.jackson.annotation.JsonIgnoreProperties import com.fasterxml.jackson.annotation.JsonProperty import javax.validation.constraints.NotNull @JsonIgnoreProperties(ignoreUnknown = true) class ReportDto( @JsonProperty("rows") @NotNull val rows: List<ReportRowDto> ) // ReportRowDto import javax.validation.constraints.NotBlank import javax.validation.constraints.NotNull @JsonIgnoreProperties(ignoreUnknown = true) class ReportRowDto ( @JsonProperty("name") @NotBlank(message = "name cant be blank") @NotNull(message = "name cant be null") val name: String ) // 异常映射器 @Provider class ValidationExceptionMapper : ExceptionMapper<ConstraintViolationException> { override fun toResponse(exception: ConstraintViolationException): Response { return Response.status(Response.Status.BAD_REQUEST) .entity("Validation failed: ${exception.message}") .type(MediaType.TEXT_PLAIN) .build() } }
依赖配置
<!-- 子pom引入的校验依赖 --> <dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-hibernate-validator</artifactId> </dependency> <!-- 父pom中无法修改的依赖 --> <dependency> <groupId>javax.annotation</groupId> <artifactId>javax.annotation-api</artifactId> <version>1.3.2</version> </dependency>
问题原因
- 响应格式预期冲突:接口方法通过
@Produces("application/pdf")声明默认返回PDF格式,Postman会以此为预期格式解析响应。当校验失败时,异常映射器返回text/plain格式的错误信息,Postman仍尝试按PDF格式解析文本内容,导致加载失败提示。 - 注解兼容性问题:父pom引入的
javax.annotation-api 1.3.2(Java EE规范)与Quarkus使用的Jakarta EE注解存在冲突,可能导致ValidationExceptionMapper未被Quarkus正确识别和注册,使得校验异常的响应信息无法正常返回。
解决方法
方法1:修复异常映射器的响应兼容性
保持接口@Produces声明不变,修改ValidationExceptionMapper,确保响应能被客户端正确识别:
@Provider @Priority(Priorities.ENTITY_CODER) // 提高优先级,确保先于默认异常处理 class ValidationExceptionMapper : ExceptionMapper<ConstraintViolationException> { override fun toResponse(exception: ConstraintViolationException): Response { val errorMsg = exception.constraintViolations.joinToString("; ") { "${it.propertyPath}: ${it.message}" } return Response.status(Response.Status.BAD_REQUEST) .entity("Validation failed: $errorMsg") .type(MediaType.TEXT_PLAIN) // 允许客户端读取Content-Type头,确保按文本格式解析 .header("Access-Control-Expose-Headers", "Content-Type") .build() } }
- 加入
@Priority注解确保异常映射器优先执行,避免被默认处理逻辑覆盖。 - 优化错误信息,拼接所有校验失败细节,提高可读性。
- 添加
Access-Control-Expose-Headers,确保客户端能正确读取Content-Type响应头,从而按文本格式解析错误信息。
方法2:排除冲突的javax注解依赖
在子pom中排除父pom引入的javax.annotation-api依赖,使用Quarkus自带的Jakarta注解:
<dependency> <groupId>javax.annotation</groupId> <artifactId>javax.annotation-api</artifactId> <version>1.3.2</version> <exclusions> <exclusion> <groupId>*</groupId> <artifactId>*</artifactId> </exclusion> </exclusions> <scope>provided</scope> <!-- 标记为provided,避免与Quarkus依赖冲突 --> </dependency>
Quarkus本身集成了Jakarta EE的注解实现,排除或标记为provided后,可解决注解冲突导致的异常映射器未注册问题。
方法3:动态指定响应格式(可选)
如果需要更灵活的响应格式控制,可以移除接口方法上的@Produces注解,改为在返回Response时动态指定:
// 移除@Produces("application/pdf") fun generateReport(@Valid reportDto: ReportDto): Response { return Response .ok(reportService.generatePdfReport(reportDto), MediaType.APPLICATION_PDF) .header("Content-Disposition", "inline; filename=\"Report.pdf\"") .build() }
这样客户端不会被固定预期格式,校验失败返回的text/plain响应能被正确解析。
内容的提问来源于stack exchange,提问作者Pavel
相关产品推荐
相关产品推荐

