Home/Learn/Spring Boot/Bean Validation

Bean Validation

Intermediate
REST & Web

Jakarta Bean Validation (@Valid, @NotBlank, @Size, @Email) validates request bodies before they reach your service. Custom validators handle business rules that built-in constraints can't express.

Overview

Spring Boot auto-configures Bean Validation (Hibernate Validator) when spring-boot-starter-validation is on the classpath. Adding @Valid to a @RequestBody parameter triggers constraint checking before the method runs — violations throw MethodArgumentNotValidException, which Spring maps to a 400 response with field-level error details. For complex cross-field validation, implement ConstraintValidator with a custom annotation.

Built-in Constraints

Jakarta Validation ships with constraints for most common cases. Apply them to DTO fields — they work on both class fields and Java record components. Nest @Valid on fields to validate inner objects.

Java — built-in constraints on a record DTO
<!-- pom.xml -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

// Request DTO with constraints
public record CreateUserRequest(
    @NotBlank(message = "Email is required")
    @Email(message = "Invalid email format")
    String email,

    @NotBlank
    @Size(min = 8, max = 100, message = "Password must be 8-100 characters")
    String password,

    @NotBlank
    @Size(min = 2, max = 100)
    String fullName,

    @Min(value = 13, message = "Must be at least 13")
    @Max(value = 120)
    Integer age,

    @Pattern(regexp = "^[6-9]\d{9}$", message = "Invalid Indian mobile number")
    String mobile,

    @Valid  // triggers nested validation
    @NotNull
    AddressDto address
) {}

public record AddressDto(
    @NotBlank String street,
    @NotBlank String city,
    @NotBlank @Size(min = 6, max = 6) String pincode
) {}

@RestController
@RequestMapping("/api/v1/users")
public class UserController {

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public UserDto createUser(@RequestBody @Valid CreateUserRequest req) {
        // Only reached if ALL constraints pass
        return userService.create(req);
    }
}

Custom Validator and Error Handling

When built-in constraints are insufficient, implement ConstraintValidator<A, T>. Returning consistent error responses from MethodArgumentNotValidException makes your API easier to consume.

Java — custom @UniqueEmail validator + global error handler
// Custom annotation
@Documented
@Constraint(validatedBy = UniqueEmailValidator.class)
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UniqueEmail {
    String message() default "Email already registered";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

// Validator implementation
@Component
public class UniqueEmailValidator implements ConstraintValidator<UniqueEmail, String> {
    private final UserRepository userRepository;

    public UniqueEmailValidator(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public boolean isValid(String email, ConstraintValidatorContext ctx) {
        if (email == null) return true; // let @NotBlank handle nulls
        return !userRepository.existsByEmail(email);
    }
}

// Use on DTO
public record CreateUserRequest(
    @UniqueEmail @Email @NotBlank String email,
    // ...
) {}

// Global error handler — consistent 400 response
@RestControllerAdvice
public class ValidationExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Map<String, Object> handleValidation(MethodArgumentNotValidException ex) {
        Map<String, String> fieldErrors = new LinkedHashMap<>();
        ex.getBindingResult().getFieldErrors()
            .forEach(e -> fieldErrors.put(e.getField(), e.getDefaultMessage()));
        return Map.of(
            "status", 400,
            "error", "Validation Failed",
            "errors", fieldErrors
        );
    }
}

Key Points to Remember

  • 1Add spring-boot-starter-validation — Bean Validation is not included in spring-boot-starter-web.
  • 2@Valid on @RequestBody triggers constraint checking; violations throw MethodArgumentNotValidException → 400.
  • 3Use @Valid on nested fields to recursively validate inner DTOs.
  • 4Implement ConstraintValidator for business-rule validation (unique email, valid coupon code, etc.).
  • 5Handle MethodArgumentNotValidException in @ControllerAdvice to return consistent, field-level error JSON.
  • 6@Validated at class level on @Service enables constraint validation on service method parameters too.

Interview Questions

Sign in to ask Aria
1

What dependency do you need for Bean Validation in Spring Boot?

EasyTCS
2

What exception is thrown when @Valid fails on a @RequestBody and what HTTP status does Spring return?

EasyWipro
3

How do you write a custom Bean Validation constraint?

MediumThoughtWorks
4

What is the difference between @Valid and @Validated?

MediumAmazon
5

How would you validate that two fields in a DTO have matching values (e.g. password confirm)?

MediumRazorpay

Ask Aria about Bean Validation

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…