The safest assumption when building an API is that clients will send you garbage: empty names, negative quantities, malformed emails, missing fields. Request validation is the wall you build at the edge of your application so that garbage is rejected with a clear message before it ever reaches your business logic. Spring Boot makes this declarative — you describe the rules with annotations and let the framework enforce them.
Validate at the boundary, not deep inside
The principle behind good validation is to reject bad input as early as possible. If you let an
invalid order object travel through three service layers before a database constraint finally
rejects it, the resulting error is confusing and expensive. Catch it at the controller, where you
can return a precise 400 Bad Request naming the exact field that is wrong.
Spring Boot implements the Bean Validation standard (Jakarta Validation) through Hibernate Validator. You annotate a request object once, and the same rules apply everywhere that object is received. This pairs naturally with the endpoints you build in the REST API with Spring Boot tutorial.
Add the validation starter
Validation lives in its own starter, so add it to your build first:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
This brings in Hibernate Validator and the constraint annotations. In older Spring Boot versions validation shipped inside the web starter, but it is now separate — a detail worth remembering, because a missing starter is the most common reason constraints appear to be ignored. If you are unsure what a starter contains, the Spring Boot starters tutorial explains how to inspect one.
Annotate the request object
Put constraints directly on the fields of the object that represents the incoming request:
import jakarta.validation.constraints.*;
public class CreateUserRequest {
@NotBlank(message = "Name is required")
@Size(min = 2, max = 50, message = "Name must be 2 to 50 characters")
private String name;
@NotBlank(message = "Email is required")
@Email(message = "Email must be valid")
private String email;
@Min(value = 18, message = "Age must be at least 18")
@Max(value = 120, message = "Age looks invalid")
private int age;
// getters and setters
}
Each annotation is a rule with a custom message. The messages matter — they become the text your
API returns, so write them for the client, not for yourself. The most useful common constraints
are @NotNull, @NotBlank, @Size, @Min, @Max, @Email and @Pattern for regex checks.
Pro tip: Know the difference between the three "not empty" annotations.
@NotNullonly rejects null.@NotEmptyrejects null and empty.@NotBlankrejects null, empty, and whitespace-only strings. For text fields a user types,@NotBlankis almost always what you actually want.
Trigger validation with @Valid
Annotations on the object do nothing until you tell Spring to check them. Add @Valid to the
controller parameter:
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/users")
public class UserController {
@PostMapping
public String create(@Valid @RequestBody CreateUserRequest request) {
// reached only if validation passed
return "Created user " + request.getName();
}
}
Now when a request arrives, Spring validates it before entering the method body. If every rule
passes, your code runs normally. If any rule fails, Spring throws
MethodArgumentNotValidException and your method is never called — the invalid data never touches
your logic. This is the enforcement point that makes the whole system trustworthy.
Common mistake: Adding constraint annotations to the fields but forgetting
@Validon the parameter. Without@Valid, Spring binds the object but never checks the rules, so invalid data sails straight through. If your constraints seem to do nothing, this is the first place to look.
Return clean validation errors
By default Spring Boot returns a verbose response when validation fails. For a polished API, capture the exception in a global handler and format it yourself, exactly as you would for any error in REST exception handling:
import org.springframework.http.*;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.*;
import java.util.*;
@RestControllerAdvice
public class ValidationHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handle(
MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(err ->
errors.put(err.getField(), err.getDefaultMessage()));
return ResponseEntity.badRequest().body(errors);
}
}
Now a bad request for the user endpoint returns a tidy 400 like
{"email":"Email must be valid","age":"Age must be at least 18"}. The client learns every problem
at once, field by field, instead of fixing one error only to hit the next. That single response
shape is what makes an API pleasant to integrate against.
Validating nested objects
If your request contains another object — say an Address inside CreateUserRequest — its
constraints are checked only if you cascade validation with @Valid on the field:
public class CreateUserRequest {
@NotBlank
private String name;
@Valid // cascade validation into Address
@NotNull
private Address address;
}
Without the inner @Valid, Spring validates the top-level name but ignores every constraint
inside Address. Cascading is opt-in by design, so you control exactly how deep validation goes.
Validating path variables and query parameters
Not all input arrives in a request body. To validate a @PathVariable or @RequestParam directly,
put the constraint on the parameter and mark the controller class with @Validated:
import org.springframework.validation.annotation.Validated;
@RestController
@RequestMapping("/api/products")
@Validated
public class ProductController {
@GetMapping("/{id}")
public String get(@PathVariable @Min(1) Long id) {
return "Product " + id;
}
}
Here @Validated at the class level enables method-parameter validation, and @Min(1) rejects a
zero or negative id with a ConstraintViolationException. This is the key distinction between the
two trigger annotations: @Valid cascades into an object's fields, while @Validated on the class
turns on validation of loose method parameters. You handle the resulting
ConstraintViolationException in your advice just as you handle body validation, keeping every
error response uniform.
Writing a custom constraint
The built-in annotations cover most needs, but domain rules sometimes require your own. A custom constraint is an annotation paired with a validator class:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PincodeValidator.class)
public @interface ValidPincode {
String message() default "Must be a 6-digit Indian pincode";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class PincodeValidator
implements ConstraintValidator<ValidPincode, String> {
@Override
public boolean isValid(String value, ConstraintValidatorContext ctx) {
return value != null && value.matches("\\d{6}");
}
}
Now @ValidPincode works exactly like a built-in constraint anywhere you place it. Custom
constraints keep validation declarative and reusable — the rule lives in one place and reads clearly
at the point of use, rather than being buried as an if check inside a service. This is the pattern
to reach for when the same business rule appears on more than one field or request across your
Spring Boot application.
Interview relevance
Validation questions test whether you build defensively. Be ready to name the common constraint
annotations, explain the crucial difference between @NotNull, @NotEmpty and @NotBlank, and
describe the two-step mechanism: constraints on the object plus @Valid on the parameter to
trigger them. A strong answer connects validation to error handling — a failed check throws
MethodArgumentNotValidException, which you turn into a clean 400. These come up together in the
Spring Boot REST API interview questions, and getting the
@Valid trigger and nested cascading right is what separates a real answer from a memorised list.
Building this habit early is one reason learners in the
Java Full Stack program ship APIs that survive real traffic.
Frequently Asked Questions
What is the difference between @Valid and @Validated?
Which dependency do I need for validation in Spring Boot?
What is the difference between @NotNull, @NotEmpty and @NotBlank?
How do I validate nested objects?
What happens when validation fails in Spring Boot?
Want to Build Your Career in Java Full Stack with AI?
Join CodeBegun and train with working industry engineers — Explore the Program

