Implementing Data Validation

1. Using Bean Validation API

Detail Value
Spec Jakarta Bean Validation 3.0
Default impl Hibernate Validator 8
Starter spring-boot-starter-validation
Package jakarta.validation.constraints.*

2. Validating Request Body (@Valid, @Validated)

Example: Validate request body

@PostMapping
public ResponseEntity<Void> create(@Valid @RequestBody NewUser body) { /* … */ }
Annotation Scope
@Valid (Jakarta) Cascade validation, basic groups via Default
@Validated (Spring) Supports validation groups + class-level on @Service

3. Using Standard Constraints

Annotation Applies To Rule
@NotNull Any Not null (allows empty)
@NotEmpty String/Coll Not null + size > 0
@NotBlank String Not null + trim() not empty
@Size(min,max) String/Coll Length / size range
@AssertTrue/@AssertFalse boolean Boolean check
@Null Any Must be null

4. Using Numeric Constraints

Constraint Description
@Min(n) / @Max(n) Long range
@DecimalMin / @DecimalMax BigDecimal range
@Positive / @PositiveOrZero > 0 / ≥ 0
@Negative / @NegativeOrZero < 0 / ≤ 0
@Digits(integer, fraction) Numeric precision

5. Using Pattern Matching

Constraint Use
@Pattern(regexp="^[A-Z0-9]{6}$") Regex match
@Email RFC email
@URL (Hibernate) Valid URL
@CreditCardNumber Luhn check
@ISBN ISBN-10/13

6. Creating Custom Validators (ConstraintValidator)

Example: Custom @ValidSku constraint annotation

@Constraint(validatedBy = SkuValidator.class)
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidSku {
  String message() default "Invalid SKU format";
  Class<?>[] groups() default {};
  Class<? extends Payload>[] payload() default {};
}

public class SkuValidator implements ConstraintValidator<ValidSku, String> {
  private static final Pattern P = Pattern.compile("^[A-Z]{3}-\\d{6}$");
  @Override public boolean isValid(String v, ConstraintValidatorContext c) {
    return v == null || P.matcher(v).matches();
  }
}

7. Handling Validation Errors (BindingResult)

Example: Return field validation errors as map

@PostMapping
public ResponseEntity<?> create(@Valid @RequestBody Form f, BindingResult br) {
  if (br.hasErrors()) {
    Map<String,String> errs = br.getFieldErrors().stream()
      .collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage));
    return ResponseEntity.badRequest().body(errs);
  }
  return ResponseEntity.ok().build();
}
Warning: BindingResult parameter MUST be immediately after the validated argument.

8. Customizing Validation Messages

Example: Custom validation message bundle

# src/main/resources/ValidationMessages.properties
NotBlank.user.email=Email is required
Size.user.password=Password must be {min} to {max} characters
javax.validation.constraints.Email.message=Provide a valid email address
Format Description
{min}, {max}, {value} Constraint params
{NotBlank.user.email} Inheritable message keys
SpEL ${} Expression evaluation

9. Using Validation Groups

Example: Validation groups for create vs update

public interface OnCreate {}
public interface OnUpdate {}

public record User(
  @Null(groups=OnCreate.class) @NotNull(groups=OnUpdate.class) Long id,
  @NotBlank String email
) {}

@PostMapping
ResponseEntity<?> create(@Validated(OnCreate.class) @RequestBody User u) { /* … */ }

10. Validating Nested Objects (@Valid on fields)

Example: Nested object and list validation

public record Order(
  @NotNull @Valid Customer customer,
  @NotEmpty @Valid List<@Valid LineItem> items
) {}
Pattern Effect
@Valid on field Cascade into nested object
List<@Valid X> Element-level cascade (Bean Val 2+)

11. Using @Validated for Method-Level Validation

Example: Method-level validation on service

@Service
@Validated
public class UserService {
  public User load(@NotBlank String email) { /* … */ }
  public @NotNull User update(@Valid UserUpdate u) { /* … */ }
}
Note: Throws ConstraintViolationException; handle in @ControllerAdvice.