Course topics

By WebNest Studio Engineering Team

Spring Boot Tutorial

Request Validation in Spring Boot

Never trust input that arrives over HTTP. Spring Boot integrates with Bean Validation (the Jakarta Validation specification, implemented by Hibernate Validator) so you can declare validation rules directly on a DTO's fields with annotations, and have Spring enforce them automatically before your controller method body even runs.

Enabling Validation and Common Annotations

Add spring-boot-starter-validation to your dependencies, then annotate DTO fields with constraint annotations from jakarta.validation.constraints.

  • @NotNull — the value must not be null.
  • @NotBlank — a String must not be null and must contain at least one non-whitespace character.
  • @Size(min, max) — bounds the length of a String or the size of a collection.
  • @Email — the String must be a well-formed email address.
  • @Min / @Max — numeric lower/upper bounds.
  • @Positive / @PositiveOrZero — numeric sign constraints.

Triggering Validation with @Valid

Adding @Valid before a @RequestBody parameter in a controller method tells Spring to run Bean Validation on the deserialized object before the method executes. If any constraint fails, Spring throws a MethodArgumentNotValidException and, by default, responds with HTTP 400 Bad Request and a body describing each failed field — before your business logic ever sees the invalid data.

Customizing the Validation Error Response

Spring Boot's default 400 response says almost nothing: {"timestamp":…,"status":400,"error":"Bad Request","path":"/api/users"}. It does not tell the client which field failed or why. Pairing @Valid with a @RestControllerAdvice that handles MethodArgumentNotValidException lets you return every field and its message in one consistent payload, so a form can highlight the right inputs.

Validating Nested Objects and Lists

Validation stops at the outer object unless you tell it to go deeper. Put @Valid on a field that holds another object, or on the element type of a list (List<@Valid OrderLine>), and each nested object is checked too. The error then names the exact path, such as lines[0].quantity. Add @NotEmpty on the list itself if an order with no lines should be rejected.

Validating Query and Path Parameters

Since Spring Framework 6.1, constraint annotations work directly on @RequestParam and @PathVariable parameters, for example @RequestParam @Min(1) @Max(100) int limit. You no longer need @Validated on the controller class for this. A failure raises HandlerMethodValidationException instead of MethodArgumentNotValidException, so a global handler needs a method for each.

Where Validation Belongs

  • At the API edge with Bean Validation: required fields, lengths, formats and ranges. These are rules about the shape of one request.
  • In the service layer: rules that need the database or other data, such as "this email is already registered" or "stock is too low". Throw a specific exception and map it to 409 or 422.
  • In the database: NOT NULL, UNIQUE and foreign-key constraints as the last line of defence, because code paths other than your API (scripts, batch jobs, other services) can write data too.

Examples

Validating a request DTO with @Valid and Bean Validation annotations

Java
public record CreateUserRequest(
    @NotBlank(message = "Name is required") String name,
    @Email(message = "Email must be valid") @NotBlank String email,
    @Size(min = 8, message = "Password must be at least 8 characters") String password
) {}

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

    @PostMapping
    public ResponseEntity<String> create(@Valid @RequestBody CreateUserRequest request) {
        return ResponseEntity.status(HttpStatus.CREATED).body("User created: " + request.name());
    }
}

// POST /api/users {"name":"", "email":"not-an-email", "password":"123"}
Output
-> 400 Bad Request   (Spring Boot's default body, with no handler of your own)
{"timestamp":"2026-10-09T21:18:38.467Z","status":400,"error":"Bad Request","path":"/api/users"}

The controller method never runs. The next example shows how to tell the client which fields failed.

Returning every failed field as an RFC 9457 ProblemDetail (tested on Spring Boot 4.1.1)

Java
public record OrderLine(@NotBlank String sku, @Positive int quantity) {}

public record CreateOrderRequest(@NotEmpty List<@Valid OrderLine> lines) {}

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

    @PostMapping("/users")
    public ResponseEntity<String> create(@Valid @RequestBody CreateUserRequest request) {
        return ResponseEntity.status(HttpStatus.CREATED).body("User created: " + request.name());
    }

    @PostMapping("/orders")
    public String order(@Valid @RequestBody CreateOrderRequest request) {
        return "order with " + request.lines().size() + " lines";
    }

    @GetMapping("/users")
    public String list(@RequestParam @Min(1) @Max(100) int limit) {
        return "listing " + limit + " users";
    }
}

@RestControllerAdvice
public class ApiExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ProblemDetail handleInvalidBody(MethodArgumentNotValidException ex) {
        Map<String, String> errors = new LinkedHashMap<>();
        for (FieldError error : ex.getBindingResult().getFieldErrors()) {
            errors.put(error.getField(), error.getDefaultMessage());
        }
        ProblemDetail problem = ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, "Some fields are invalid");
        problem.setTitle("Validation failed");
        problem.setProperty("errors", errors);
        return problem;
    }

    @ExceptionHandler(HandlerMethodValidationException.class)
    public ProblemDetail handleInvalidParameter(HandlerMethodValidationException ex) {
        Map<String, String> errors = new LinkedHashMap<>();
        ex.getParameterValidationResults().forEach(result -> result.getResolvableErrors().forEach(error ->
                errors.put(result.getMethodParameter().getParameterName(), error.getDefaultMessage())));
        ProblemDetail problem = ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, "Some parameters are invalid");
        problem.setTitle("Validation failed");
        problem.setProperty("errors", errors);
        return problem;
    }
}
Output
POST /api/users {"name":"","email":"not-an-email","password":"123"}
-> 400 {"detail":"Some fields are invalid","instance":"/api/users","status":400,"title":"Validation failed",
        "errors":{"password":"Password must be at least 8 characters","email":"Email must be valid","name":"Name is required"}}

POST /api/orders {"lines":[{"sku":"A1","quantity":0}]}
-> 400 {..., "errors":{"lines[0].quantity":"must be greater than 0"}}

POST /api/orders {"lines":[]}
-> 400 {..., "errors":{"lines":"must not be empty"}}

GET /api/users?limit=500
-> 400 {"detail":"Some parameters are invalid", ..., "errors":{"limit":"must be less than or equal to 100"}}

(The order of fields inside "errors" can change between runs.)

Common Mistakes

  • Writing a handler only for MethodArgumentNotValidException. Constraint failures on @RequestParam and @PathVariable raise HandlerMethodValidationException instead, and fall back to the bare default 400.
  • Annotating a list field with @NotEmpty but not its elements with @Valid, so every line item inside the list goes unchecked.
  • Forgetting @Valid on the controller parameter — the constraint annotations exist on the DTO but are silently never checked.
  • Forgetting to add spring-boot-starter-validation, so @NotNull/@NotBlank/etc. compile but have no runtime effect.
  • Validating only at the controller layer and assuming that protects the database — internal service calls or batch jobs bypassing the controller still need their own checks.
  • Not handling MethodArgumentNotValidException, leaving clients to parse Spring's default verbose error structure.

Exercise

Using the lesson's Validating a request DTO with @Valid and Bean Validation annotations example, create a small implementation or design sketch that demonstrates Bean Validation annotations (@NotNull, @NotBlank, @Size, @Email, @Min/@Max) declare rules directly on DTO fields.. Add a test case or scenario that would catch Writing a handler only for MethodArgumentNotValidException. Constraint failures on @RequestParam and @PathVariable raise HandlerMethodValidationException instead, and fall back to the bare default 400., and describe the expected result.

Show hints
  • Start with Enabling Validation and Common Annotations and compare your approach with Validating a request DTO with @Valid and Bean Validation annotations. Identify the decision or behavior the lesson recommends.
  • Walk through your scenario step by step. Confirm that it follows Bean Validation annotations (@NotNull, @NotBlank, @Size, @Email, @Min/@Max) declare rules directly on DTO fields. and that it would catch the failure described by Writing a handler only for MethodArgumentNotValidException. Constraint failures on @RequestParam and @PathVariable raise HandlerMethodValidationException instead, and fall back to the bare default 400..

Quick quiz

1. Which choice is a mistake to avoid in Request Validation in Spring Boot?

  • A. Annotating a list field with @NotEmpty but not its elements with @Valid, so every line item inside the list goes unchecked.
  • B. Bean Validation annotations (@NotNull, @NotBlank, @Size, @Email, @Min/@Max) declare rules directly on DTO fields.
  • C. Writing a handler only for MethodArgumentNotValidException. Constraint failures on @RequestParam and @PathVariable raise HandlerMethodValidationException instead, and fall back to the bare default 400.
Show answer

C. Avoid this approach: Writing a handler only for MethodArgumentNotValidException. Constraint failures on @RequestParam and @PathVariable raise HandlerMethodValidationException instead, and fall back to the bare default 400. The lesson recommends: Bean Validation annotations (@NotNull, @NotBlank, @Size, @Email, @Min/@Max) declare rules directly on DTO fields.

Interview questions

A teammate is about to make this mistake: Writing a handler only for MethodArgumentNotValidException. Constraint failures on @RequestParam and @PathVariable raise HandlerMethodValidationException instead, and fall back to the bare default 400. How would you diagnose and correct it using the lesson's guidance?

Show answer

Bean Validation annotations (@NotNull, @NotBlank, @Size, @Email, @Min/@Max) declare rules directly on DTO fields. Apply this in Enabling Validation and Common Annotations; the example Validating a request DTO with @Valid and Bean Validation annotations shows one way to do it. Avoid Writing a handler only for MethodArgumentNotValidException. Constraint failures on @RequestParam and @PathVariable raise HandlerMethodValidationException instead, and fall back to the bare default 400. and explain how your implementation or design prevents that failure.

Key Points to Remember

  • Bean Validation annotations (@NotNull, @NotBlank, @Size, @Email, @Min/@Max) declare rules directly on DTO fields.
  • @Valid on a @RequestBody parameter triggers validation automatically before the controller method runs.
  • Failed validation throws MethodArgumentNotValidException, which defaults to an HTTP 400 response.
  • Pair @Valid with a global exception handler to produce a clean, consistent validation error format.
  • Use @Valid on nested objects and list elements (List<@Valid T>) to validate them too.
  • Constraints on @RequestParam/@PathVariable work without @Validated since Spring 6.1 and raise HandlerMethodValidationException.

Practice in the playground

Write and run your own code for this topic, or start from the exercise or an example. Runs plain Java 17. Frameworks such as Spring, and databases, are not available here. Your code for each topic is kept in this browser.

Run the examples

Change an input, predict the result, then compare it with the output. Explain why the result changes.

These examples need a JDK project with their libraries on your computer, so they can be copied but not run here. Codelab runs Python, FastAPI backends with SQLite, plain Java 17 and HTML/CSS/JavaScript.

WebNest Studio

Written by

WebNest Studio Engineering Team

Practical lessons from developers working with Java, Python, React, databases and production systems.

Led by Vansh Duggal, Founder, WebNest Studio · Full-stack & software engineering