Validation
So you want to make sure users actually send good data to your API? Validation is your best friend here. Nobody wants a database full of garbage โ and Spring Boot makes it dead simple to prevent that.
Think of validation like a bouncer at a club. It checks every piece of data coming in and says "you're good" or "get out." No bad data gets past the velvet rope.
The @Valid Annotation
The magic starts with @Valid. You slap this on a request body parameter, and Spring Boot automatically runs all the validation rules you've defined on that object. It's like telling Spring "hey, check this before you use it."
Without @Valid, your validation annotations are just decorations sitting there doing nothing. With it, they actually enforce the rules.
@PostMapping("/users")
public ResponseEntity<User> createUser(@Valid @RequestBody User user) {
User saved = userService.save(user);
return ResponseEntity.ok(saved);
}
Common Validation Annotations
Spring Boot gives you a bunch of annotations out of the box. @NotNull ensures a value isn't null, @NotBlank makes sure a string isn't empty or just whitespace, and @Size controls the length of strings or the size of collections.
For numbers, you've got @Min and @Max to set boundaries. @Email checks that a string looks like a proper email address. These are your bread and butter for everyday validation.
public class User {
@NotBlank(message = "Name is required")
@Size(min = 2, max = 50, message = "Name must be 2-50 characters")
private String name;
@NotNull(message = "Email is required")
@Email(message = "Please provide a valid email")
private String email;
@Min(value = 18, message = "Must be at least 18")
@Max(value = 120, message = "Age cannot exceed 120")
private int age;
}
Custom Validators
Sometimes the built-in annotations aren't enough. Maybe you need to check that a phone number follows a specific format, or that a username is unique in your database. That's where custom validators come in.
You create a custom annotation and a validator class that implements ConstraintValidator. It's like teaching the bouncer a new trick โ suddenly it can handle any rule you throw at it.
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PhoneValidator.class)
public @interface Phone {
String message() default "Invalid phone number";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class PhoneValidator implements ConstraintValidator<Phone, String> {
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return value != null && value.matches("^\\+?[0-9]{10,13}$");
}
}
Just remember to handle validation errors properly โ either with @ControllerAdvice or by catching MethodArgumentNotValidException. Otherwise your users will just see cryptic 500 errors instead of helpful messages.