Java Jakarta Validation 实战指南

 更新时间:2026年07月17日 09:23:52   作者:唐青枫  
Jakarta Validation 是 Java 生态里用于数据校验的标准规范, 它最常见的使用方式,就是在请求对象、实体对象、方法参数上加注解,下面就来详细的了解一下

简介

Jakarta Validation 是 Java 生态里用于数据校验的标准规范。

它最常见的使用方式,就是在请求对象、实体对象、方法参数上加注解:

public record CreateUserRequest(
        @NotBlank(message = "用户名不能为空")
        String username,

        @Email(message = "邮箱格式不正确")
        String email,

        @Min(value = 18, message = "年龄不能小于18岁")
        Integer age
) {
}

然后在接口入口处触发校验:

@PostMapping("/users")
public Long create(@Valid @RequestBody CreateUserRequest request) {
    return userService.create(request);
}

请求参数不符合规则时,Spring 会在进入业务方法前抛出校验异常。

它的价值很直接:把参数规则写在模型上,把错误返回集中处理,让业务代码少写一堆重复的 if 判断。

Jakarta Validation、Hibernate Validator、Spring Boot 的关系

这几个名字经常一起出现,但角色不一样。

名称角色说明
Jakarta Validation规范定义注解、API、校验模型
Hibernate Validator实现Jakarta Validation 的常见参考实现
Spring Boot集成方自动配置 Validator,并在 Web、Service 等场景触发校验

简单理解:

Jakarta Validation 定规则
Hibernate Validator 负责执行规则
Spring Boot 负责把校验接到 Web 请求、方法调用和异常处理流程里

包名也有一个历史变化:

时代包名
Java EE / Bean Validation 老项目javax.validation.*
Jakarta EE / Spring Boot 3+jakarta.validation.*

Spring Boot 3 开始整体切到 Jakarta 命名空间,所以代码里通常使用:

import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;

Maven 依赖

Spring Boot 项目通常直接引入 validation starter:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

它会带上 Jakarta Validation API 和 Hibernate Validator。

如果是普通 Java 项目,可以直接引入 Hibernate Validator:

<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>${hibernate-validator.version}</version>
</dependency>

部分 Java SE 场景还需要表达式语言实现,用于处理更复杂的消息插值:

<dependency>
    <groupId>org.glassfish.expressly</groupId>
    <artifactId>expressly</artifactId>
    <version>${expressly.version}</version>
</dependency>

Spring Boot 项目优先交给 Boot 的依赖管理,不建议在业务项目里随意手写 Hibernate Validator 版本。

第一个 Spring Boot Demo

先准备一个创建用户请求对象。

package com.example.user.dto;

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;

public record CreateUserRequest(

        @NotBlank(message = "用户名不能为空")
        @Size(min = 2, max = 20, message = "用户名长度需要在2到20之间")
        String username,

        @NotBlank(message = "邮箱不能为空")
        @Email(message = "邮箱格式不正确")
        String email,

        @NotNull(message = "年龄不能为空")
        @Min(value = 18, message = "年龄不能小于18岁")
        @Max(value = 100, message = "年龄不能大于100岁")
        Integer age,

        @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
        String phone
) {
}

Controller:

package com.example.user.controller;

import com.example.user.dto.CreateUserRequest;
import com.example.user.dto.UserCreateResponse;
import com.example.user.service.UserService;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/users")
public class UserController {

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @PostMapping
    public UserCreateResponse create(@Valid @RequestBody CreateUserRequest request) {
        Long userId = userService.create(request);
        return new UserCreateResponse(userId);
    }
}

响应对象:

package com.example.user.dto;

public record UserCreateResponse(Long userId) {
}

Service:

package com.example.user.service;

import com.example.user.dto.CreateUserRequest;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    public Long create(CreateUserRequest request) {
        return System.currentTimeMillis();
    }
}

请求参数不合法时,Controller 方法不会继续执行,Spring 会抛出绑定异常。

常用注解

常用约束可以按类型记。

注解适用场景说明
@NotNull任意对象不能为 null
@NotEmpty字符串、集合、数组、Map不能为 null,长度或大小不能为 0
@NotBlank字符串不能为 null,去掉空白后不能是空字符串
@Size字符串、集合、数组、Map限制长度或大小
@Min / @Max整数、长整数等数字限制最小值和最大值
@DecimalMin / @DecimalMaxBigDecimal 等数字适合金额、比例
@Positive数字需要大于 0
@PositiveOrZero数字需要大于等于 0
@Negative数字需要小于 0
@Digits数字限制整数位和小数位
@Email字符串邮箱格式
@Pattern字符串正则表达式
@Past日期时间应为过去时间
@PastOrPresent日期时间应为过去或当前时间
@Future日期时间应为未来时间
@FutureOrPresent日期时间应为未来或当前时间
@AssertTrue布尔值应为 true
@AssertFalse布尔值应为 false

几个注解很容易混:

注解null空字符串 ""空白字符串 " "
@NotNull不通过通过通过
@NotEmpty不通过不通过通过
@NotBlank不通过不通过不通过

用户名、标题、备注这类字符串必填字段,通常用 @NotBlank。

集合必填并且至少有一个元素,通常用 @NotEmpty。

数字、日期、对象必填,通常用 @NotNull。

@Valid 和 @Validated 的区别

@Valid 来自 Jakarta Validation:

import jakarta.validation.Valid;

@Validated 来自 Spring:

import org.springframework.validation.annotation.Validated;

常见区别如下:

注解来源常见用途
@ValidJakarta Validation触发对象校验、级联校验
@ValidatedSpring触发分组校验、方法参数校验

JSON 请求体校验通常这样写:

@PostMapping
public UserCreateResponse create(@Valid @RequestBody CreateUserRequest request) {
    return new UserCreateResponse(1001L);
}

如果要使用分组校验,通常用 @Validated

@PostMapping
public UserCreateResponse create(
        @Validated(CreateGroup.class) @RequestBody UserRequest request
) {
    return new UserCreateResponse(1001L);
}

如果要校验 @RequestParam@PathVariable 这种普通参数,Controller 类或方法通常需要加 @Validated

package com.example.user.controller;

import jakarta.validation.constraints.Min;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Validated
@RestController
@RequestMapping("/api/users")
public class UserQueryController {

    @GetMapping("/{id}")
    public String getById(@PathVariable @Min(value = 1, message = "用户ID需要大于0") Long id) {
        return "user-" + id;
    }
}

全局异常处理

参数校验失败后,默认错误响应通常不适合直接给前端使用。

可以用 @RestControllerAdvice 统一包装错误结果。

先定义统一错误对象:

package com.example.common;

import java.util.List;

public record ApiErrorResponse(
        String code,
        String message,
        List<FieldErrorItem> errors
) {
}

字段错误对象:

package com.example.common;

public record FieldErrorItem(
        String field,
        String message
) {
}

全局异常处理器:

package com.example.common;

import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.HandlerMethodValidationException;

import java.util.ArrayList;
import java.util.List;

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ApiErrorResponse handleRequestBodyValidException(MethodArgumentNotValidException ex) {
        List<FieldErrorItem> errors = ex.getBindingResult()
                .getFieldErrors()
                .stream()
                .map(this::toFieldErrorItem)
                .toList();

        return new ApiErrorResponse("VALIDATION_FAILED", "参数校验失败", errors);
    }

    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ApiErrorResponse handleConstraintViolationException(ConstraintViolationException ex) {
        List<FieldErrorItem> errors = ex.getConstraintViolations()
                .stream()
                .map(this::toFieldErrorItem)
                .toList();

        return new ApiErrorResponse("VALIDATION_FAILED", "参数校验失败", errors);
    }

    @ExceptionHandler(HandlerMethodValidationException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ApiErrorResponse handleHandlerMethodValidationException(HandlerMethodValidationException ex) {
        List<FieldErrorItem> errors = new ArrayList<>();

        ex.visitResults(new HandlerMethodValidationException.Visitor() {
            @Override
            public void other(org.springframework.validation.method.ParameterValidationResult result) {
                result.getResolvableErrors().forEach(error -> {
                    String field = result.getMethodParameter().getParameterName();
                    errors.add(new FieldErrorItem(field, error.getDefaultMessage()));
                });
            }
        });

        return new ApiErrorResponse("VALIDATION_FAILED", "参数校验失败", errors);
    }

    private FieldErrorItem toFieldErrorItem(FieldError error) {
        return new FieldErrorItem(error.getField(), error.getDefaultMessage());
    }

    private FieldErrorItem toFieldErrorItem(ConstraintViolation<?> violation) {
        return new FieldErrorItem(
                violation.getPropertyPath().toString(),
                violation.getMessage()
        );
    }
}

常见异常来源:

异常常见来源
MethodArgumentNotValidException@RequestBody 对象校验失败
ConstraintViolationExceptionService 方法参数校验、部分普通参数校验
HandlerMethodValidationExceptionSpring MVC 方法参数校验失败

不同 Spring 版本和注解位置可能抛出不同异常,统一处理时可以一起覆盖。

分组校验

新增用户和修改用户经常有不同规则。

比如新增时没有 id,修改时需要传 id

先定义两个分组接口:

package com.example.user.validation;

public interface CreateGroup {
}
package com.example.user.validation;

public interface UpdateGroup {
}

请求对象:

package com.example.user.dto;

import com.example.user.validation.CreateGroup;
import com.example.user.validation.UpdateGroup;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;

public record UserRequest(

        @NotNull(message = "用户ID不能为空", groups = UpdateGroup.class)
        Long id,

        @NotBlank(message = "用户名不能为空", groups = {CreateGroup.class, UpdateGroup.class})
        @Size(min = 2, max = 20, message = "用户名长度需要在2到20之间", groups = {CreateGroup.class, UpdateGroup.class})
        String username,

        @NotBlank(message = "邮箱不能为空", groups = CreateGroup.class)
        @Email(message = "邮箱格式不正确", groups = {CreateGroup.class, UpdateGroup.class})
        String email
) {
}

Controller:

package com.example.user.controller;

import com.example.user.dto.UserRequest;
import com.example.user.validation.CreateGroup;
import com.example.user.validation.UpdateGroup;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/users")
public class UserGroupController {

    @PostMapping
    public String create(@Validated(CreateGroup.class) @RequestBody UserRequest request) {
        return "创建成功";
    }

    @PutMapping
    public String update(@Validated(UpdateGroup.class) @RequestBody UserRequest request) {
        return "修改成功";
    }
}

分组适合规则差异比较明确的场景。

如果新增和修改字段差异很大,拆成 CreateUserRequestUpdateUserRequest 两个 DTO 通常更清楚。

级联校验

对象里嵌套对象时,需要在嵌套字段上加 @Valid

地址对象:

package com.example.order.dto;

import jakarta.validation.constraints.NotBlank;

public record AddressRequest(

        @NotBlank(message = "省份不能为空")
        String province,

        @NotBlank(message = "城市不能为空")
        String city,

        @NotBlank(message = "详细地址不能为空")
        String detail
) {
}

订单对象:

package com.example.order.dto;

import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;

public record CreateOrderRequest(

        @NotBlank(message = "订单号不能为空")
        String orderNo,

        @Valid
        @NotNull(message = "收货地址不能为空")
        AddressRequest address
) {
}

没有 @Valid 时,只会校验 address 是否为 null,不会继续校验 AddressRequest 里面的 provincecitydetail

集合和泛型元素校验

Jakarta Validation 支持容器元素校验。

比如订单明细列表:

package com.example.order.dto;

import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;

import java.util.List;

public record SubmitOrderRequest(

        @NotEmpty(message = "订单明细不能为空")
        List<@Valid OrderItemRequest> items,

        List<@NotNull(message = "优惠券ID不能为空") Long> couponIds
) {
}

明细对象:

package com.example.order.dto;

import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;

public record OrderItemRequest(

        @NotNull(message = "商品ID不能为空")
        Long skuId,

        @NotNull(message = "购买数量不能为空")
        @Positive(message = "购买数量需要大于0")
        Integer count
) {
}

几个细节:

写法含义
@NotEmpty List<OrderItemRequest> items列表本身不能为空,且至少有一个元素
List<@Valid OrderItemRequest> items校验列表里每个对象
List<@NotNull Long> couponIds校验列表里每个 ID 不能为 null

Service 方法参数校验

校验不只发生在 Controller。

Service 方法参数也可以校验,常见于内部服务、定时任务、消息消费入口。

package com.example.user.service;

import jakarta.validation.Valid;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

@Validated
@Service
public class UserQueryService {

    public String getUsername(@Min(value = 1, message = "用户ID需要大于0") Long userId) {
        return "user-" + userId;
    }

    public void changeNickname(
            @Min(value = 1, message = "用户ID需要大于0") Long userId,
            @NotBlank(message = "昵称不能为空") String nickname
    ) {
        // 更新昵称
    }

    public void create(@Valid CreateUserCommand command) {
        // 创建用户
    }
}

命令对象:

package com.example.user.service;

import jakarta.validation.constraints.NotBlank;

public record CreateUserCommand(
        @NotBlank(message = "用户名不能为空")
        String username
) {
}

Service 类上加 @Validated 后,Spring 会通过代理触发方法校验。

同一个类内部直接调用本类方法时,可能绕过代理,方法校验不会触发。需要把被校验的方法放到另一个 Spring Bean,或者通过代理对象调用。

返回值校验

方法返回值也可以加约束。

package com.example.user.service;

import jakarta.validation.constraints.NotNull;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

@Validated
@Service
public class UserProfileService {

    @NotNull(message = "用户资料不能为空")
    public UserProfile findProfile(Long userId) {
        return new UserProfile(userId, "Tom");
    }
}

返回对象:

package com.example.user.service;

public record UserProfile(
        Long userId,
        String username
) {
}

返回值校验更适合内部服务契约,不适合滥用。很多业务场景下,返回为空本身可能就是合法结果。

自定义校验注解

内置注解覆盖不了所有业务规则。

比如手机号格式,可以封装成一个注解。

先定义注解:

package com.example.validation;

import jakarta.validation.Constraint;
import jakarta.validation.Payload;

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

@Documented
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PhoneNumberValidator.class)
public @interface PhoneNumber {

    String message() default "手机号格式不正确";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

再写校验器:

package com.example.validation;

import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;

import java.util.regex.Pattern;

public class PhoneNumberValidator implements ConstraintValidator<PhoneNumber, String> {

    private static final Pattern PHONE_PATTERN = Pattern.compile("^1[3-9]\\d{9}$");

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (value == null || value.isBlank()) {
            return true;
        }
        return PHONE_PATTERN.matcher(value).matches();
    }
}

使用:

package com.example.user.dto;

import com.example.validation.PhoneNumber;
import jakarta.validation.constraints.NotBlank;

public record BindPhoneRequest(

        @NotBlank(message = "手机号不能为空")
        @PhoneNumber
        String phone
) {
}

自定义校验器里通常把 null 当作通过。

原因是是否必填应该交给 @NotNull@NotBlank 这类注解表达。这样一个 @PhoneNumber 既可以用于必填手机号,也可以用于非必填手机号。

类级别校验

有些规则需要同时看多个字段。

比如开始时间不能晚于结束时间。

先定义注解:

package com.example.validation;

import jakarta.validation.Constraint;
import jakarta.validation.Payload;

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

@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = DateRangeValidator.class)
public @interface ValidDateRange {

    String message() default "开始时间不能晚于结束时间";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

请求对象:

package com.example.activity.dto;

import com.example.validation.ValidDateRange;
import jakarta.validation.constraints.FutureOrPresent;
import jakarta.validation.constraints.NotNull;

import java.time.LocalDateTime;

@ValidDateRange
public record ActivityRequest(

        @NotNull(message = "开始时间不能为空")
        @FutureOrPresent(message = "开始时间不能早于当前时间")
        LocalDateTime startTime,

        @NotNull(message = "结束时间不能为空")
        @FutureOrPresent(message = "结束时间不能早于当前时间")
        LocalDateTime endTime
) {
}

校验器:

package com.example.validation;

import com.example.activity.dto.ActivityRequest;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;

public class DateRangeValidator implements ConstraintValidator<ValidDateRange, ActivityRequest> {

    @Override
    public boolean isValid(ActivityRequest value, ConstraintValidatorContext context) {
        if (value == null || value.startTime() == null || value.endTime() == null) {
            return true;
        }
        return !value.startTime().isAfter(value.endTime());
    }
}

类级别校验适合跨字段规则。单字段规则仍然放在字段注解上更直观。

手动校验

脱离 Spring Web 时,也可以手动使用 Validator

package com.example.validation;

import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import jakarta.validation.ValidatorFactory;

import java.util.Set;

public class ManualValidationDemo {

    public static void main(String[] args) {
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        Validator validator = factory.getValidator();

        RegisterCommand command = new RegisterCommand("", "bad-email");

        Set<ConstraintViolation<RegisterCommand>> violations = validator.validate(command);

        for (ConstraintViolation<RegisterCommand> violation : violations) {
            System.out.println(violation.getPropertyPath() + " -> " + violation.getMessage());
        }
    }
}

命令对象:

package com.example.validation;

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;

public record RegisterCommand(

        @NotBlank(message = "用户名不能为空")
        String username,

        @Email(message = "邮箱格式不正确")
        String email
) {
}

输出大概是:

username -> 用户名不能为空
email -> 邮箱格式不正确

错误消息和国际化

注解里的 message 可以直接写中文:

@NotBlank(message = "用户名不能为空")
private String username;

也可以写消息 key:

@NotBlank(message = "{user.username.notBlank}")
private String username;

然后在 src/main/resources/ValidationMessages.properties 中配置:

user.username.notBlank=用户名不能为空
user.email.invalid=邮箱格式不正确

Spring Boot 也会结合应用的 MessageSource 解析消息。项目已经有 messages.propertiesmessages_zh_CN.properties 这类国际化文件时,可以把校验文案纳入统一管理。

Fail Fast:只返回第一个错误

默认情况下,一个对象里多个字段不合法,会返回多个错误。

有些接口只想返回第一个错误,可以开启 Hibernate Validator 的 fail fast。

Spring Boot 里可以注册一个配置:

package com.example.config;

import org.hibernate.validator.HibernateValidatorConfiguration;
import org.springframework.boot.autoconfigure.validation.ValidationConfigurationCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ValidationConfig {

    @Bean
    public ValidationConfigurationCustomizer validationConfigurationCustomizer() {
        return configuration -> {
            if (configuration instanceof HibernateValidatorConfiguration hibernateConfiguration) {
                hibernateConfiguration.failFast(true);
            }
        };
    }
}

多错误返回适合表单场景,一次性展示所有字段问题。

单错误返回适合移动端弹窗、命令式接口、对响应体大小比较敏感的场景。

和 JPA 实体校验的关系

Jakarta Validation 可以放在 JPA 实体上。

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.validation.constraints.NotBlank;

@Entity
public class UserEntity {

    @Id
    @GeneratedValue
    private Long id;

    @NotBlank(message = "用户名不能为空")
    private String username;
}

Hibernate ORM 在持久化生命周期中也可以触发校验。

不过 Web 接口里更推荐使用专门的请求 DTO:

CreateUserRequest 负责接口入参
UserEntity 负责数据库映射

接口字段和数据库字段经常不完全一致。把校验规则全部堆到实体上,后期容易被不同接口场景互相影响。

常见问题

引入了注解但校验没有生效

常见原因:

原因处理方式
缺少 spring-boot-starter-validation添加 validation starter
@RequestBody 前没有 @Valid 或 @Validated在参数前添加触发注解
普通参数校验缺少 @Validated在 Controller 或 Service 类上添加 @Validated
Service 内部调用本类方法通过 Spring 代理调用,或拆到另一个 Bean
嵌套对象缺少 @Valid在嵌套字段或集合元素上添加 @Valid

int 加了 @NotNull 仍然没效果

int 是基本类型,默认值是 0,不会是 null。

如果要表达必填,使用包装类型:

@NotNull(message = "年龄不能为空")
private Integer age;

@NotBlank 用在 Integer 上报错

@NotBlank 只能用于字符串。

数字必填用 @NotNull,数值范围用 @Min、@Max、@Positive 等注解。

@NotNull(message = "数量不能为空")
@Positive(message = "数量需要大于0")
private Integer count;

@Valid 和 BindingResult 的位置

Spring MVC 支持在被校验参数后面紧跟 BindingResult 手动处理错误。

@PostMapping
public String create(@Valid @RequestBody CreateUserRequest request,
                     BindingResult bindingResult) {
    if (bindingResult.hasErrors()) {
        return bindingResult.getFieldError().getDefaultMessage();
    }
    return "创建成功";
}

这种写法适合少量特殊接口。

大多数 REST API 更适合用全局异常处理器统一返回错误结构。

实践建议

场景建议
Web JSON 入参DTO 上写约束,Controller 参数使用 @Valid
普通参数Controller 类或 Service 类使用 @Validated
新增和修改规则差异小使用分组校验
新增和修改字段差异大拆成不同请求 DTO
嵌套对象嵌套字段上加 @Valid
集合元素使用 List<@Valid Item> 或 List<@NotNull Long>
业务格式规则封装自定义注解
错误返回使用 @RestControllerAdvice 统一处理
JPA 实体避免把所有接口规则都压到实体上

小结

Jakarta Validation 的核心是声明式校验。

简单字段规则用内置注解,跨字段规则用类级别自定义注解,业务格式规则用自定义约束,接口错误返回交给全局异常处理器。

Spring Boot 项目中,常见组合是:

spring-boot-starter-validation
DTO 约束注解
Controller @Valid / @Validated
Service @Validated
@RestControllerAdvice

这样参数校验、业务逻辑和错误返回会分得比较清楚,代码也更容易维护。

到此这篇关于Java Jakarta Validation 实战指南的文章就介绍到这了,更多相关Java Jakarta Validation 内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • JAVA和C#的语法特性及优缺点对比

    JAVA和C#的语法特性及优缺点对比

    Java和C#是当今流行的两种面向对象的编程语言,它们都源自C语言的语法风格,但各自发展出了独特的特性,这篇文章主要介绍了JAVA和C#的语法特性及优缺点对比的相关资料,需要的朋友可以参考下
    2025-11-11
  • visual studio2022 JNI开发流程的实现

    visual studio2022 JNI开发流程的实现

    本文主要介绍了通过IDEA创建Maven项目并生成JNI头文件,使用Visual Studio 2022构建DLL,最后在Java中加载并调用C++实现,具有一定的参考价值,感兴趣的可以了解一下
    2025-07-07
  • Java设计模式之动态代理模式实例分析

    Java设计模式之动态代理模式实例分析

    这篇文章主要介绍了Java设计模式之动态代理模式,结合实例形式分析了动态代理模式的概念、功能、组成、定义与使用方法,需要的朋友可以参考下
    2018-04-04
  • springboot使用shiro-整合redis作为缓存的操作

    springboot使用shiro-整合redis作为缓存的操作

    这篇文章主要介绍了springboot使用shiro-整合redis作为缓存的操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-06-06
  • java集合_浅谈Iterable和Iterator的区别

    java集合_浅谈Iterable和Iterator的区别

    下面小编就为大家带来一篇java集合_浅谈Iterable和Iterator的区别。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-09-09
  • Spring Boot如何优雅的使用多线程实例详解

    Spring Boot如何优雅的使用多线程实例详解

    这篇文章主要给大家介绍了关于Spring Boot如何优雅的使用多线程的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用Spring Boot具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2020-05-05
  • java中equals和等号(==)的区别浅谈

    java中equals和等号(==)的区别浅谈

    java中equals和等号(==)的区别浅谈,需要的朋友可以参考一下
    2013-05-05
  • Java数据结构二叉树难点解析

    Java数据结构二叉树难点解析

    树是一种重要的非线性数据结构,直观地看,它是数据元素(在树中称为结点)按分支关系组织起来的结构,很象自然界中的树那样。树结构在客观世界中广泛存在,如人类社会的族谱和各种社会组织机构都可用树形象表示
    2021-10-10
  • maven profile动态选择配置文件详解

    maven profile动态选择配置文件详解

    这篇文章主要介绍了maven profile动态选择配置文件详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-11-11
  • 解决IDEA报错java无效的目标发行版:22

    解决IDEA报错java无效的目标发行版:22

    在使用IDEA编译项目时,可能会遇到JDK版本不一致的错误,这篇文章主要介绍了解决IDEA报错java无效的目标发行版:22的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2024-10-10

最新评论