Input Validation with Bean Validation (@Valid)


Table of Contents

  1. Introduction
  2. Bean Validation Overview
  3. Using @Valid for Input Validation
  4. Custom Validation Annotations
  5. Handling Validation Errors
  6. Conclusion

1. Introduction

In any application, validating user input is a critical task to ensure that the data processed by your system is both correct and safe. In Spring Boot, the Bean Validation framework is used to perform automatic validation of Java beans (objects) using annotations.

Spring Boot supports Java Bean Validation through the javax.validation package, which is integrated with Spring’s validation framework. By using the @Valid annotation, you can trigger validation on the request body, method parameters, and more.

In this module, we will explore the basics of Bean Validation, demonstrate how to use the @Valid annotation for input validation, and discuss handling validation errors.


2. Bean Validation Overview

Bean Validation is a specification for validating JavaBeans, which are simple objects with getter and setter methods for properties. It provides a set of built-in constraints (annotations) to validate fields in your objects.

Common Validation Annotations:

  • @NotNull: Ensures that a field is not null.
  • @Size(min, max): Validates the length of a string, array, or collection.
  • @Min(value): Ensures that a number is greater than or equal to a specified value.
  • @Max(value): Ensures that a number is less than or equal to a specified value.
  • @Pattern(regex): Validates that a string matches a regular expression.
  • @Email: Validates that a string is a well-formed email address.

Example of Bean Validation in Java:

javaCopyEditpublic class User {
    @NotNull
    private String name;

    @Email
    private String email;

    @Min(18)
    private int age;

    // Getters and Setters
}

In this example, the User class uses the @NotNull, @Email, and @Min annotations to validate the input data for a user object.


3. Using @Valid for Input Validation

The @Valid annotation is used to trigger validation on an object. It can be applied to method parameters or fields in controllers to validate the data before processing it further.

Validating Method Parameters:

When a request body is passed to a controller method, you can use the @Valid annotation to automatically validate it.

javaCopyEdit@RestController
public class UserController {

    @PostMapping("/api/users")
    public ResponseEntity<User> createUser(@Valid @RequestBody User user) {
        // If the user object is invalid, validation errors will be automatically returned
        return ResponseEntity.ok(user);
    }
}

In this example:

  • The @Valid annotation is used on the user parameter in the createUser method.
  • The @RequestBody annotation is used to bind the incoming JSON request to the User object.
  • Spring will automatically trigger validation when the createUser method is called.
  • If the user data is invalid (e.g., missing name or invalid email), Spring will return a 400 Bad Request with validation error messages.

Validating Request Parameters:

You can also use the @Valid annotation with query parameters, path variables, or form parameters.

javaCopyEdit@GetMapping("/api/users/{id}")
public ResponseEntity<User> getUser(@Valid @PathVariable("id") Long id) {
    // Validates the id (if you apply constraints like @Min, @Max, etc.)
    User user = userService.getUserById(id);
    return ResponseEntity.ok(user);
}

4. Custom Validation Annotations

In some cases, you may need to create custom validation logic that isn’t covered by the built-in constraints. You can define custom annotations and validators by implementing ConstraintValidator.

Example of a Custom Validator:

Suppose you need to validate that a user’s age is within a specific range, say between 18 and 120. You can create a custom validation annotation as follows:

Custom Annotation:

javaCopyEdit@Target({ ElementType.FIELD, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = AgeValidator.class)
public @interface ValidAge {
    String message() default "Invalid age";

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

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

Validator Implementation:

javaCopyEditpublic class AgeValidator implements ConstraintValidator<ValidAge, Integer> {

    @Override
    public void initialize(ValidAge constraintAnnotation) {
        // Initialization code if necessary
    }

    @Override
    public boolean isValid(Integer age, ConstraintValidatorContext context) {
        if (age == null) {
            return false; // Null values are invalid
        }
        return age >= 18 && age <= 120;
    }
}

Using the Custom Annotation:

javaCopyEditpublic class User {
    @ValidAge
    private int age;

    // Getters and Setters
}

With this setup:

  • The @ValidAge annotation is applied to the age field in the User class.
  • The AgeValidator checks if the value of age is between 18 and 120.
  • If the validation fails, the appropriate error message ("Invalid age") will be returned.

5. Handling Validation Errors

Once validation is triggered, you need to handle errors effectively to inform the user about the issues in their input. In Spring Boot, this can be done by creating an exception handler method that will catch validation errors.

Using @Valid with BindingResult:

When you use @Valid, Spring automatically populates a BindingResult object with validation errors if they occur. You can check this object to handle errors in your controller.

javaCopyEdit@PostMapping("/api/users")
public ResponseEntity<Object> createUser(@Valid @RequestBody User user, BindingResult bindingResult) {
    if (bindingResult.hasErrors()) {
        List<String> errorMessages = bindingResult.getAllErrors().stream()
                .map(DefaultMessageSourceResolvable::getDefaultMessage)
                .collect(Collectors.toList());
        
        return ResponseEntity.badRequest().body(errorMessages);
    }

    return ResponseEntity.ok(user);
}

Example of Global Exception Handling:

You can also handle validation exceptions globally by creating an exception handler in a @ControllerAdvice class.

javaCopyEdit@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Object> handleValidationExceptions(MethodArgumentNotValidException ex) {
        List<String> errorMessages = ex.getBindingResult().getAllErrors().stream()
                .map(DefaultMessageSourceResolvable::getDefaultMessage)
                .collect(Collectors.toList());
        
        return ResponseEntity.badRequest().body(errorMessages);
    }
}

In this example:

  • If validation fails, a 400 Bad Request response is returned with a list of error messages.
  • The global exception handler catches any MethodArgumentNotValidException, which is thrown when validation fails.

6. Conclusion

In this module, we’ve learned how to use Bean Validation in Spring Boot to validate input data effectively. We’ve covered the following topics:

  • Bean Validation: How to use built-in annotations like @NotNull, @Email, and @Min to validate user input.
  • @Valid Annotation: How to use @Valid to trigger automatic validation in controller methods.
  • Custom Validation: How to create custom validation annotations and implement custom logic for validation.
  • Handling Validation Errors: How to handle validation errors and return meaningful error messages to clients.

By using Bean Validation in your Spring Boot applications, you can ensure that incoming data is validated properly, and handle errors gracefully, providing a better user experience.