Skillversitas

Data Validation

Introduction

Data validation ensures that input from the client follows business rules before it is processed. Spring Boot supports Jakarta Validation (Bean Validation), which makes it easy to declare validation rules directly on your models.

Dependency

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

Common Validation Annotations

Annotation Purpose Example
@NotNull Must not be null @NotNull Long id
@NotBlank Must not be null/empty/spaces @NotBlank String name
@Size(min, max) Length of a string/list @Size(min = 3, max = 50)
@Min(value) Minimum value @Min(0) int age
@Max(value) Maximum value @Max(100) int score
@Email Valid email format @Email String email
@Pattern(regex) Matches a regex @Pattern(regexp = "^[0-9]+$")

Entity Example with Validation

public class UserRequest {

    @NotBlank(message = "Name is required")
    @Size(min = 3, max = 100, message = "Name must be 3-100 characters")
    private String name;

    @NotBlank(message = "Email is required")
    @Email(message = "Invalid email format")
    private String email;

    @NotBlank(message = "Password is required")
    @Size(min = 8, message = "Password must be at least 8 characters")
    private String password;

    @Min(value = 17, message = "Age must be at least 17 years")
    private int age;
}

Using @Valid in a Controller

The @Valid annotation on a controller parameter triggers automatic validation.

@PostMapping
public ResponseEntity<User> create(@Valid @RequestBody UserRequest request) {
    User user = userService.create(request);
    return ResponseEntity.status(HttpStatus.CREATED).body(user);
}

@PutMapping("/{id}")
public ResponseEntity<User> update(
        @PathVariable Long id,
        @Valid @RequestBody UserRequest request) {
    User user = userService.update(id, request);
    return ResponseEntity.ok(user);
}

Handling Validation Errors

Spring automatically returns a MethodArgumentNotValidException when validation fails. Handle it with @ExceptionHandler.

@RestControllerAdvice
public class ValidationExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Map<String, List<String>>> handleValidation(
            MethodArgumentNotValidException ex) {

        List<String> errors = ex.getBindingResult()
            .getFieldErrors()
            .stream()
            .map(error -> error.getField() + ": " + error.getDefaultMessage())
            .collect(Collectors.toList());

        Map<String, List<String>> response = new HashMap<>();
        response.put("errors", errors);

        return ResponseEntity.badRequest().body(response);
    }
}

Response:

{
  "errors": [
    "name: Name is required",
    "email: Invalid email format",
    "password: Password must be at least 8 characters"
  ]
}

Custom Validators

Create custom validation rules using an annotation and a ConstraintValidator.

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

    @Autowired
    private UserRepository userRepository;

    @Override
    public boolean isValid(String email, ConstraintValidatorContext context) {
        return email != null && !userRepository.existsByEmail(email);
    }
}

Summary

  • Use @Valid in controllers to trigger validation.
  • Jakarta Validation provides ready-to-use annotations.
  • Handle errors with @RestControllerAdvice.

Next chapter: We'll cover Exception Handling — how to handle errors globally so your API returns consistent error responses.