Spring Boot Validation

Validation checks that the data sent by a client meets your rules before your app processes it. Reject bad data at the API boundary — do not let it reach your service layer or database.

Adding the Validation Dependency

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

Annotating a Request DTO

Place constraint annotations on the fields of your request class:

public class CreateUserRequest {

    @NotBlank(message = "Name is required")
    @Size(min = 2, max = 50, message = "Name must be 2–50 characters")
    private String name;

    @NotBlank(message = "Email is required")
    @Email(message = "Must be a valid email address")
    private String email;

    @NotNull(message = "Age is required")
    @Min(value = 18, message = "Must be at least 18")
    @Max(value = 120, message = "Must be 120 or below")
    private Integer age;

    @NotNull
    @Pattern(regexp = "^[+]?[0-9]{10,13}$", message = "Invalid phone number")
    private String phone;

    // Getters and setters
}

Triggering Validation in the Controller

Add @Valid to the @RequestBody parameter. Spring runs validation before the method body executes:

@PostMapping("/users")
public ResponseEntity<UserResponse> createUser(
    @Valid @RequestBody CreateUserRequest request   ← @Valid triggers validation
) {
    UserResponse saved = userService.create(request);
    return ResponseEntity.status(201).body(saved);
}

If validation fails, Spring throws MethodArgumentNotValidException before your method runs.

Handling Validation Errors

Add this to your global exception handler (covered in the Exception Handling topic):

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handleValidation(
    MethodArgumentNotValidException ex
) {
    Map<String, String> errors = new LinkedHashMap<>();
    ex.getBindingResult().getFieldErrors().forEach(err ->
        errors.put(err.getField(), err.getDefaultMessage())
    );
    return ResponseEntity.badRequest().body(errors);
}

Client receives a clear error response:

HTTP 400 Bad Request
{
  "name": "Name is required",
  "email": "Must be a valid email address",
  "age": "Must be at least 18"
}

Common Validation Annotations

Annotation                  Validates
──────────────────────────  ──────────────────────────────────────────
@NotNull                    Value is not null
@NotBlank                   String is not null, empty, or whitespace
@NotEmpty                   String or collection is not null or empty
@Size(min, max)             String/collection length is within range
@Min(value)                 Number is >= value
@Max(value)                 Number is <= value
@Email                      Valid email format
@Pattern(regexp)            Matches a regular expression
@Positive                   Number is greater than 0
@PositiveOrZero             Number is >= 0
@Future                     Date is in the future
@Past                       Date is in the past
@DecimalMin / @DecimalMax   Decimal number constraints

Validation Flow Diagram

  Client sends POST /users with body

       │
       ▼ @Valid
  Spring validates request fields
       │
       ├── All valid? ──▶ Method runs normally → 201 Created
       │
       └── Invalid? ──▶ MethodArgumentNotValidException
                              │
                              ▼
                    GlobalExceptionHandler
                              │
                              ▼
                    400 Bad Request with field errors

Validating Path Variables and Query Params

Add @Validated at the class level to validate non-body parameters:

@RestController
@Validated                             ← Required for method-level validation
@RequestMapping("/users")
public class UserController {

    @GetMapping("/{id}")
    public User getUser(
        @PathVariable @Positive(message = "ID must be positive") Long id
    ) {
        return userService.findById(id);
    }
}

Custom Validation Annotation

Create a reusable custom constraint when built-in annotations are not enough:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = UniqueEmailValidator.class)
public @interface UniqueEmail {
    String message() default "Email already registered";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}
public class UniqueEmailValidator
    implements ConstraintValidator<UniqueEmail, String> {

    @Autowired
    private UserRepository userRepo;

    @Override
    public boolean isValid(String email, ConstraintValidatorContext ctx) {
        return !userRepo.existsByEmail(email);
    }
}

Summary

  • Add spring-boot-starter-validation to enable Bean Validation
  • Annotate DTO fields with @NotBlank, @Email, @Min, and other constraints
  • Add @Valid to the @RequestBody parameter to trigger validation
  • Handle MethodArgumentNotValidException in your global exception handler
  • Create custom annotations for business-rule validations like unique email checks

Leave a Comment

Your email address will not be published. Required fields are marked *