Master Spring Boot 3 Validation: 9 Essential Annotation Techniques

This article walks through Spring Boot 3's powerful annotation‑based parameter validation, covering basic constraints, custom validators, group validation, nested objects, method‑level checks, internationalized messages, programmatic validation, composite annotations, and cross‑field verification with complete code examples.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Master Spring Boot 3 Validation: 9 Essential Annotation Techniques

Introduction

Ensuring that method parameters are valid is essential for stable systems. Traditional validation code is verbose and hard to maintain, while annotation‑based validation (e.g., Spring Validation) simplifies this by using annotations such as @NotNull and @Size on fields or method parameters.

Practical Cases

2.1 Basic Annotations

Spring Validation provides a set of ready‑to‑use constraints. Example:

public class UserDTO {
  private Long id;

  @NotBlank(message = "用户名必须填写")
  @Size(min = 4, max = 20, message = "用户名必须是4到20个字符")
  private String username;

  @Email(message = "无效的邮箱")
  private String email;

  @Min(value = 18, message = "年龄必须大于18")
  private Integer age;

  @Max(value = 120, message = "年龄必须小于120")
  private Integer age;

  @Past(message = "错误的出生日期")
  private LocalDate birthDate;

  @Pattern(regexp = "^1[3-9]\d{9}$", message = "电话号码错误")
  private String phone;
}

Controller method validation:

@RestController
@RequestMapping("/api/users")
public class UserController {
  @PostMapping
  public ResponseEntity<UserDTO> createUser(@RequestBody @Valid UserDTO userDTO, BindingResult bindingResult) {
    if (bindingResult.hasErrors()) {
      throw new ValidationException(bindingResult);
    }
    return ResponseEntity.ok(userDTO);
  }
}

2.2 Custom Annotation Validation

Define a custom constraint:

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = UniqueUsernameValidator.class)
public @interface UniqueUsername {
  String message() default "用户名已存在";
  Class<?>[] groups() default {};
  Class<? extends Payload>[] payload() default {};
}

Validator implementation:

public class UniqueUsernameValidator implements ConstraintValidator<UniqueUsername, String> {
  @Resource
  private UserRepository userRepository;

  @Override
  public boolean isValid(String username, ConstraintValidatorContext context) {
    if (username == null) {
      return true; // @NotNull handles null
    }
    return !userRepository.existsByUsername(username);
  }
}

2.3 Group Validation

Define validation groups for different scenarios:

public interface ValidationGroups {
  interface Create {}
  interface Update {}
}

Apply groups to a DTO:

public class ProductDTO {
  @Null(groups = ValidationGroups.Create.class, message = "创建商品时ID必须为空")
  @NotNull(groups = ValidationGroups.Update.class, message = "更新商品时商品ID不能为空")
  private Long id;

  @NotBlank(groups = {ValidationGroups.Create.class, ValidationGroups.Update.class})
  private String name;
}

Controller usage:

@PostMapping
public ResponseEntity<ProductDTO> createProduct(@RequestBody @Validated(ValidationGroups.Create.class) ProductDTO productDTO) {
  return ResponseEntity.ok(productDTO);
}

@PutMapping("/{id}")
public ResponseEntity<ProductDTO> updateProduct(@PathVariable Long id, @RequestBody @Validated(ValidationGroups.Update.class) ProductDTO productDTO) {
  return ResponseEntity.ok(productDTO);
}

2.4 Nested Validation

Validate nested objects and collections:

public class OrderDTO {
  @NotNull
  @Valid // nested validation
  private CustomerDTO customer;

  @NotEmpty
  @Valid // nested validation for each item
  private List<OrderItemDTO> items;
}

public class CustomerDTO {
  @NotBlank
  private String name;

  @Email
  private String email;

  @Valid
  private AddressDTO address;
}

2.5 Method‑Level Validation

Apply validation in service layer:

@Service
@Validated
public class UserService {
  public User createUser(@Valid UserDTO userDTO) {
    return new User();
  }

  @NotNull
  public User findById(@Min(1) Long id) {
    return new User();
  }
}

Note: When validating controller methods, @Validated on the class is not required.

2.6 Error Message Internationalization

Direct message definition:

@NotEmpty(message = "姓名不能为空")
private String name;

Using placeholders with resource bundles:

@NotEmpty(message = "{name.empty.error}")
private String name;

Resource files:

# messages_zh_CN.properties
name.empty.error=姓名必须填写
address.empty.error=地址必须填写

# messages_en_US.properties
name.empty.error=nameEmptyError
address.empty.error=addressEmptyError

2.7 Programmatic Validation

Manually trigger validation when needed:

@Service
public class ValidationService {
  private final Validator validator;

  public ValidationService(Validator validator) {
    this.validator = validator;
  }

  public <T> void validate(T object) {
    Set<ConstraintViolation<T>> violations = validator.validate(object);
    if (!violations.isEmpty()) {
      throw new ConstraintViolationException(violations);
    }
  }
}

2.8 Composite Validation

Combine multiple constraints into a single reusable annotation:

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {})
public @interface StrongPassword {
  String message() default "密码不符合安全要求";
  Class<?>[] groups() default {};
  Class<? extends Payload>[] payload() default {};
}

public class PasswordChangeDTO {
  @StrongPassword
  private String newPassword;
}

2.9 Cross‑Field Validation

Class‑level constraint to ensure two fields match (e.g., password and confirm password):

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PasswordMatchesValidator.class)
public @interface PasswordMatches {
  String message() default "密码不匹配";
  String field();
  String fieldMatch();
  Class<?>[] groups() default {};
  Class<? extends Payload>[] payload() default {};
}

public class PasswordMatchesValidator implements ConstraintValidator<PasswordMatches, Object> {
  private String field;
  private String fieldMatch;

  @Override
  public void initialize(PasswordMatches constraintAnnotation) {
    this.field = constraintAnnotation.field();
    this.fieldMatch = constraintAnnotation.fieldMatch();
  }

  @Override
  public boolean isValid(Object value, ConstraintValidatorContext context) {
    try {
      Field f1 = value.getClass().getDeclaredField(field);
      f1.setAccessible(true);
      Object v1 = f1.get(value);

      Field f2 = value.getClass().getDeclaredField(fieldMatch);
      f2.setAccessible(true);
      Object v2 = f2.get(value);

      if (v1 == null) {
        return v2 == null;
      }
      return v1.equals(v2);
    } catch (NoSuchFieldException | IllegalAccessException e) {
      throw new RuntimeException("Failed to validate password fields", e);
    }
  }
}

@PasswordMatches(field = "password", fieldMatch = "confirmPassword", message = "两次密码不匹配")
public class UserRegistrationDTO {
  @NotBlank(message = "密码不能为空")
  @Size(min = 8, message = "密码长度必须大于等于8个字符")
  private String password;

  @NotBlank(message = "确认密码不能为空")
  private String confirmPassword;
}

@PostMapping("/register")
public ResponseEntity<?> register(@Valid @RequestBody UserRegistrationDTO dto, BindingResult result) {
  if (result.hasErrors()) {
    List<String> errors = result.getAllErrors().stream()
        .map(err -> err.getDefaultMessage())
        .toList();
    return ResponseEntity.badRequest().body(errors);
  }
  return ResponseEntity.ok("Registration successful");
}

Conclusion

The article demonstrates a complete set of Spring Boot 3 validation techniques, from simple field constraints to advanced custom and cross‑field validators, including internationalization and programmatic approaches.

Image
Image
JavaSpring BootAnnotationsSpring Validation
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.