Hook: Interviewers love this question because @Valid is the gatekeeper that stops bad REST input before it turns into a production bug.
Question: What does validation using @Valid mean in Spring Boot REST APIs?
Answer: @Valid tells Spring to check an incoming object against Bean Validation rules such as @NotBlank, @Email, and @NotNull. If the object breaks those rules, Spring rejects the request with a 400 Bad Request instead of calling your business code. In Spring Boot 3, the annotations come from jakarta.validation, and the usual validation engine is Hibernate Validator.
Interview-Ready Answer: I use @Valid on controller inputs so Spring automatically validates the request DTO before my endpoint logic runs. If a field is missing or wrong, Spring returns a 400 and I can send back clean field errors instead of letting bad data reach the service layer. The important detail is that @Valid is the trigger, while the actual rules live on the DTO using constraints like @NotBlank and @Email.
@Valid really does@Valid is a standard Bean Validation annotation. Bean Validation means a set of rules for checking Java objects, usually at the boundary of your app, such as REST requests. Spring Boot wires this into MVC so that when a controller method receives a DTO, Spring can ask the validator to inspect it.
@Valid on the parameter, it asks the Bean Validation engine to validate that object.@NotBlank or @Email.@Valid, Spring cascades validation into that nested object too. Cascade means it keeps walking the object graph instead of stopping at the first level.@RequestBody, the usual exception is MethodArgumentNotValidException, which you can convert into a friendly error payload.Use @Valid for API input that must be structurally correct before your app can trust it: names, emails, required IDs, nested address objects, and so on. It is ideal for request DTOs because it keeps your controller thin and your service layer free from repetitive null checks. For complex business rules, use custom validators or service checks, because @Valid is best at input shape and basic correctness, not database logic.
@Valid vs @Validated| Aspect | @Valid | @Validated |
|---|---|---|
| Origin | Jakarta Bean Validation | Spring annotation |
| Best use | DTO validation | Method and group validation |
| Validation groups | No | Yes |
| Service methods | Limited | Common choice |
| Typical import | jakarta.validation.Valid | org.springframework.validation.annotation.Validated |
Simple rule: use @Valid for request bodies, and reach for @Validated when you need method-level checks or validation groups.
Validation cost is roughly O(n) in the number of constrained fields plus nested objects. For a normal REST DTO, the work is tiny, often microseconds to a low millisecond range. The real slowdown comes from custom validators that hit a database or another service.
null unless you add @NotNull. For strings, @NotBlank catches empty or whitespace-only input.@Valid on the nested field, or the inner object will not be checked.jakarta.validation; older Boot 2 code often used javax.validation.Memory hook: think of @Valid as a bouncer at the REST door: it checks the guest list before anyone enters the club.
Imagine a checkout service for an e-commerce app. The frontend sends a customer name, email, shipping address, and phone number. Without @Valid, an empty email or missing address can slip into the service layer and fail later when the app tries to send a receipt or save the order. That turns a simple client mistake into a noisy 500 error.
What goes wrong in production? You start seeing logs like ConstraintViolationException in some places, NullPointerException in others, and customer support reports that checkout sometimes “spins” and then fails. The user impact is painful: orders are not created, payment may already be authorized, and your team has to reconcile broken state. With @Valid, the API returns a clean 400 immediately, the UI can highlight the bad field, and the failure stays cheap and understandable.
// File: src/main/java/com/example/validationdemo/ValidationApplication.java
// Requires Spring Boot 3.x and the spring-boot-starter-validation dependency.
package com.example.validationdemo;
import jakarta.validation.ConstraintViolationException;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.List;
import java.util.stream.Collectors;
@SpringBootApplication
public class ValidationApplication {
public static void main(String[] args) {
SpringApplication.run(ValidationApplication.class, args);
}
}
// @Valid on the request body triggers Bean Validation before the method logic runs.
@RestController
@RequestMapping("/api")
@Validated // Needed for method-level validation like @Positive on path variables.
class UserController {
@PostMapping("/users")
public ResponseEntity<UserResponse> createUser(@Valid @RequestBody CreateUserRequest request) {
// If the request is invalid, Spring throws MethodArgumentNotValidException and never enters here.
return ResponseEntity.status(HttpStatus.CREATED)
.body(new UserResponse(1L, request.name(), request.email(), request.address().city()));
}
@GetMapping("/users/{id}")
public UserResponse getUser(@PathVariable @Positive Long id) {
// This shows the edge case: method parameter validation uses @Validated at the class level.
return new UserResponse(id, "Alice", "alice@example.com", "London");
}
}
record CreateUserRequest(
@NotBlank(message = "name is required")
String name,
@NotBlank(message = "email is required")
@Email(message = "email must be valid")
String email,
@NotNull(message = "address is required")
@Valid
AddressRequest address,
// Optional field: @Min ignores null, so this field is allowed to be absent.
@Min(value = 18, message = "age must be at least 18")
Integer age
) { }
record AddressRequest(
@NotBlank(message = "street is required")
String street,
@NotBlank(message = "city is required")
String city
) { }
record UserResponse(Long id, String name, String email, String city) { }
record FieldErrorDto(String field, String message) { }
record ApiError(String message, List<FieldErrorDto> errors) { }
@RestControllerAdvice
class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
ResponseEntity<ApiError> handleInvalidBody(MethodArgumentNotValidException ex) {
List<FieldErrorDto> fieldErrors = ex.getBindingResult().getFieldErrors().stream()
.map(err -> new FieldErrorDto(err.getField(), err.getDefaultMessage()))
.collect(Collectors.toList());
return ResponseEntity.badRequest()
.body(new ApiError("Validation failed", fieldErrors));
}
@ExceptionHandler(ConstraintViolationException.class)
ResponseEntity<ApiError> handleConstraintViolations(ConstraintViolationException ex) {
List<FieldErrorDto> fieldErrors = ex.getConstraintViolations().stream()
.map(v -> new FieldErrorDto(v.getPropertyPath().toString(), v.getMessage()))
.collect(Collectors.toList());
return ResponseEntity.badRequest()
.body(new ApiError("Validation failed", fieldErrors));
}
}
/*
Try these requests:
1) Valid request
POST /api/users
{
"name": "Sara",
"email": "sara@example.com",
"address": { "street": "1 Main St", "city": "Berlin" },
"age": 22
}
2) Invalid request body
POST /api/users
{
"name": " ",
"email": "not-an-email",
"address": { "street": "", "city": "" },
"age": 15
}
3) Invalid path variable
GET /api/users/0
The first request returns 201, while the invalid ones return 400 with field-level errors.
*/Follow-up & Tricky Questions:
@Valid fails on @RequestBody? Spring raises MethodArgumentNotValidException and returns 400. In real apps, you usually catch it with @RestControllerAdvice so the client gets readable field errors.@Valid on the nested field, and use validation annotations on the nested DTO itself. For collections, validate each element so one bad child item does not slip through.@Validated instead of @Valid? Use @Validated when you need validation groups or method parameter validation such as @PathVariable, @RequestParam, or service methods. @Valid is the standard trigger for object graphs like request DTOs.BindingResult or MethodArgumentNotValidException and map them into your own response model. That keeps the API stable and user-friendly.@Valid validate null by itself? No. Most constraints ignore null; you need @NotNull if the field must be present. This is one of the most common misunderstandings.@Valid enough for a PATCH endpoint? Often no. PATCH usually means partial data, so a create-style DTO with many required fields will reject legitimate partial updates. Use a dedicated DTO or validation groups.@Valid automatically check database rules? No. It checks object state, not business truth. If you need to confirm an ID exists or a username is unique, that logic belongs in a custom validator or service.Common Mistakes:
spring-boot-starter-validation so Spring has a validator on the classpath.@NotBlank for non-strings or expecting it to catch null everywhere. Fix: choose the right constraint: @NotNull for presence, @NotBlank for text, @Positive for numbers.@Valid on the controller but forgetting nested @Valid on child objects. Fix: cascade validation explicitly into nested DTOs.Memory Hook: Think: “Valid before value.” The request must pass the bouncer before your code gives it business meaning.
Cheat Sheet:
@Valid triggers Bean Validation on a DTO.@NotBlank, @Email, and @NotNull.@RequestBody input usually becomes 400 Bad Request.@Validated for groups and method parameter checks.@Valid again.null unless you add @NotNull.Practice Tasks:
List<AddressRequest> field and make sure each item is validated.@StrongPassword and reject weak passwords.