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

Spring Boot中Jakarta枚举自定义验证失效,需按自定义规则抛错

问题:自定义枚举验证器未触发,反序列化先抛出异常

问题场景

自定义了枚举验证器和注解,用于限制DTO中枚举字段的可选值,但传入非法字符串时,先触发Jackson反序列化异常,自定义验证逻辑完全没执行。

自定义验证器代码

package my.dtos.enums;

import my.dtos.CourseStatus;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;

import java.util.Arrays;

public class CourseStatusValidator implements ConstraintValidator<CourseStatusType, CourseStatus> {
    private CourseStatus[] subset;

    @Override
    public void initialize(CourseStatusType constraint) {
        this.subset = constraint.anyOf();
    }

    @Override
    public boolean isValid(CourseStatus value, ConstraintValidatorContext context) {
        return value == null || Arrays.asList(subset).contains(value);
    }
}

注解定义代码

package my.dtos.enums;

import my.dtos.CourseStatus;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = CourseStatusValidator.class)
public @interface CourseStatusType {
    CourseStatus[] anyOf();
    String message() default "must be any of {anyOf}";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

DTO使用代码

@CourseStatusType(anyOf = {CourseStatus.PENDING, CourseStatus.CANCELED})
private CourseStatus status;

控制器代码

@PutMapping("/{id}")
public Course update(@PathVariable("id") Long id, @RequestBody @Validated CourseRequest request)

错误信息

org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type com.some.path.CourseStatus from String "WRONG_DATA_PASSWED": not one of the values accepted for Enum class: [CANCELED, DONE, IN_PROGRESS, PENDING]

原因分析

Spring MVC的执行顺序是JSON反序列化(Jackson)优先于Bean Validation(@Validated)。当传入的字符串不属于CourseStatus枚举的合法值时,Jackson会直接抛出反序列化异常,请求根本到不了自定义验证器执行的阶段。

解决方案

通过自定义Jackson反序列化器,对非法枚举值做容错处理,让反序列化流程正常完成,之后触发自定义验证逻辑。

步骤1:编写容错枚举反序列化器

package my.dtos.enums;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import my.dtos.CourseStatus;

import java.io.IOException;

public class TolerantCourseStatusDeserializer extends JsonDeserializer<CourseStatus> {
    @Override
    public CourseStatus deserialize(JsonParser parser, DeserializationContext context) throws IOException {
        String inputValue = parser.getText().trim();
        // 匹配合法枚举值
        for (CourseStatus status : CourseStatus.values()) {
            if (status.name().equals(inputValue)) {
                return status;
            }
        }
        // 非法值返回null,交给验证器处理
        return null;
    }
}

步骤2:在DTO字段上绑定反序列化器

修改DTO中的字段定义,添加@JsonDeserialize注解指定自定义反序列化器:

@CourseStatusType(anyOf = {CourseStatus.PENDING, CourseStatus.CANCELED})
@JsonDeserialize(using = TolerantCourseStatusDeserializer.class)
private CourseStatus status;

可选优化:调整验证错误提示

修改CourseStatusType注解的默认提示信息,让用户更清晰:

String message() default "课程状态必须是{PENDING, CANCELED}中的有效值";

替代方案:映射非法值到特定枚举

如果不想返回null,可以在CourseStatus枚举中新增一个INVALID枚举项,反序列化时将非法值映射为该枚举:

// 枚举类新增项
public enum CourseStatus {
    PENDING, CANCELED, DONE, IN_PROGRESS, INVALID
}

// 反序列化器修改返回值
return CourseStatus.INVALID;

此时验证器会检测到INVALID不在允许的子集里,触发自定义错误提示。

效果验证

完成上述配置后,传入非法字符串时,Jackson会将其转为null或INVALID,反序列化流程正常完成,随后自定义验证器执行,检查值是否在指定子集内,最终抛出自定义的验证错误信息,而非Jackson的反序列化异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 15:40:09