Validation and Global Exception Handling in Spring Boot 3

Introduction

An API is not production-ready if it accepts invalid input silently or returns inconsistent error messages. In this chapter, you will learn how to validate request data with Bean Validation and how to centralize exception handling with @RestControllerAdvice. By the end, your API responses will be cleaner, safer, and easier for frontend clients to consume.

Prerequisites

  • Completed:
    • 06_spring_mvc_basics_annotations_and_routing.md
  • Basic understanding of DTOs and @RequestBody
  • A Spring Boot 3 project with spring-boot-starter-web

Why Validation and Error Handling Matter

Without validation:

  • invalid emails, empty names, or negative values enter your system
  • errors show up later in service or database layers

Without global exception handling:

  • each controller handles errors differently
  • frontend gets unpredictable response structures

Goal:

  • validate early at controller boundary
  • return a consistent error JSON format

Add Validation Dependency

If not already included, add:

xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

This starter brings Jakarta Bean Validation (jakarta.validation.*) support.

Define Request DTO with Constraints

Common annotations:

  • @NotBlank
  • @NotNull
  • @Size
  • @Email
  • @Min, @Max, @Positive

Trigger Validation in Controller

Key point:

  • @Valid is what activates validation on the request body

If constraints fail, Spring throws MethodArgumentNotValidException.

Design a Unified API Response

Use one structure for both success and failure:

This keeps client-side parsing consistent.

Global Exception Handler with @RestControllerAdvice

What this gives you:

  • centralized validation error output
  • fallback handler for unexpected exceptions
  • stable API contract for frontend teams

Example Error Response

When request body is invalid:

json
{
  "code": 40001,
  "message": "validation failed",
  "data": {
    "username": "username must not be blank",
    "email": "email format is invalid"
  }
}

This is much better than raw stack traces or framework-default payload noise.

Validate Query Parameters and Path Variables

To validate method parameters (@RequestParam, @PathVariable), add @Validated on controller:

If validation fails here, Spring may throw ConstraintViolationException (you can add a dedicated handler for it).

Add Handler for ConstraintViolationException

Best Practices

  • Keep validation rules in request DTOs, not scattered in services
  • Provide readable validation messages for API consumers
  • Return structured error payloads with stable code values
  • Do not expose internal exception details in production responses
  • Log server-side exception details, but send safe messages externally

Warning

Avoid returning raw ex.getMessage() for unknown exceptions in production APIs. Internal details can leak implementation or security-sensitive information.

Quick Recap

In this chapter, you learned:

  1. How to validate input using Bean Validation annotations
  2. How to trigger validation with @Valid / @Validated
  3. How to centralize exception handling with @RestControllerAdvice
  4. How to design consistent error responses for clients

Next chapter will continue the web layer by covering file upload/download and practical MVC extension points.

FAQ

Do I need both @Valid and @Validated?

Use @Valid for validating object graphs (commonly request DTOs). Use @Validated to enable method-level validation (like query/path parameters) and optional validation groups.

Why is validation not triggered in my controller?

Most common reasons:

  • missing spring-boot-starter-validation
  • forgot @Valid on @RequestBody parameter
  • constraints placed on fields that are never bound

Should validation happen in controller or service?

Basic input format/required checks should happen at controller boundary. Business-rule validation can still happen in service layer.

Can I customize HTTP status for validation errors?

Yes. In your global exception handler, return ResponseEntity with whichever status and body contract your API standard requires.

How should I define error codes?

Use a small, documented catalog (for example 40001 validation, 401xx auth, 50000 unknown server error). Keep codes stable over time.