REST API Validation

👉 Master Spring Boot Property, Parameter, and Return Type validation in controllers.


What is API validation ?

API Validation is the process of ensuring that the data sent by a client (like a mobile app or a web browser) meets specific criteria before your application logic processes it.

Think of it as a security guard at the entrance of the application. If a user tries to create an account but forgets to include an email, or provides a password that is too short, validation catches these errors immediately and sends a clear message back to the user.

Maven Dependency

To enable automatic validation support in Spring Boot REST controllers and service layers, you need the following starter dependency. It bundles the required validation API and implementation (like Hibernate Validator).

Key PointDetail
PurposeIntegrates Jakarta Bean Validation with Spring Boot.
Artifactspring-boot-starter-validation
UsageEssential for annotations like @Valid, @Size, @Past, etc.
<!-- pom.xml -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

Validation

Validation in a Spring Boot REST API typically involves using the @Valid annotation on a method parameter to trigger validation on the request body (DTO/Bean).

Define validation for Controller Request

The @Valid annotation on the @RequestBody parameter tells Spring to check the constraints (like @Size, @Past) defined inside the User class before executing the method logic. If validation fails, Spring throws a MethodArgumentNotValidException, which is typically handled globally by a Spring Boot application to return a 400 Bad Request error.

import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.RestController;
// ... other imports ...

@RestController
public class UserController {

    // ... service and logger ...

    @PostMapping("/users")
    public ResponseEntity<User> createUser(@Valid @RequestBody User user) {

        logger.debug("User to save : {}", user);

        var savedUser = userDaoService.save(user);

        var location = ServletUriComponentsBuilder.fromCurrentRequest()
			.path("{id}")
			.buildAndExpand(savedUser.getId())
            .toUri();
        return ResponseEntity.created(location).body(savedUser);
    }
}

Validataion on Model properties

The constraints themselves are defined within the User POJO using annotations from the jakarta.validation.constraints.* package.

AnnotationPurpose
@SizeChecks if the annotated element’s size (for Strings, Collections, Maps) is between the specified boundaries.
@PastChecks if the annotated element (Date, Calendar, or Joda Time equivalents) is a date in the past.
import com.fasterxml.jackson.annotation.JsonFormat;
import jakarta.validation.constraints.Past;
import jakarta.validation.constraints.Size;
import java.time.LocalDate;

public class User {

    private Integer id;

    @Size(min = 3, max = 20, message = "Name must be more than 2 characters.")
    private String name;

    @Past(message = "Birth date should be in past.")
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
    private LocalDate birthDate;

    // constructors, setter-getters and other methods.
}

Project Reference:
Check the project POC on Github - Property Validation.


Validation Scope

The Jakarta Bean Validation API, leveraged by Spring, allows validation on different scopes: Property, Method Parameter, and Method Return Type.

1. Method Parameter Validation (Most Common)

  • Use Case: Validating the body of a POST/PUT request (as demonstrated above), or a path/query parameter.
  • Mechanism: Use @Valid on the @RequestBody or use constraint annotations directly on method parameters (e.g., @Min on a query parameter).

2. Property Validation (Demonstrated Above)

  • Use Case: Defining constraints on the fields of a bean (DTO/Model).
  • Mechanism: Direct use of constraint annotations (e.g., @Size, @NotNull, @Email) on fields within the class. This is the source of the rules.

3. Method Return Type Validation

  • Use Case: Validating the object returned by a method. Useful for ensuring that data retrieved from a database or another service adheres to constraints before being sent to the client. Requires using @Validated at the class level.
  • Limitation: Less common and can add performance overhead.

4. Property Validation (Cascading)

  • Use Case: When a DTO/Model contains another DTO/Model, and you want to validate the constraints on the nested object as well.
  • Mechanism: Place @Valid on the nested property within the parent class.

Validation Request Flow

The following diagram illustrates the standard validation flow for a REST API call.

sequenceDiagram
    participant C as Client
    participant SB as Spring Boot App
    participant V as Validator (Jakarta)

    C->>SB: POST /users (Request Body)
    SB->>SB: Pre-check for @Valid on parameter
    alt Validation Required
        SB->>V: Validate User Object
        V->>V: Check @Size, @Past constraints
        alt Validation Fails
            V-->>SB: ConstraintViolations
            SB->>SB: Handle MethodArgumentNotValidException
            SB-->>C: 400 Bad Request (Error Details)
        else Validation Success
            V-->>SB: Validation Success
            SB->>SB: Execute Controller Method
            SB->>SB: Service Logic
            SB-->>C: 201 Created (User)
        end
    else No @Valid
        SB->>SB: Execute Controller Method
        SB->>SB: Service Logic
        SB-->>C: Success Response
    end

Global Exception Handling

When a client sends an invalid request body to a Spring Boot controller annotated with @Valid, the framework throws a MethodArgumentNotValidException. We use a Global Exception Handler component (annotated with @ControllerAdvice) to intercept this exception and transform it into a clear, structured JSON response.

Exception Handler Component

We define a class annotated with @ControllerAdvice to make it applicable across all controllers. The key method uses @ExceptionHandler(MethodArgumentNotValidException.class) to specifically handle validation failures.

  • Key Action: Iterates through all field errors present in the exception object.
  • Response Status: Uses @ResponseStatus(HttpStatus.BAD_REQUEST) to ensure the HTTP status code is correctly set to 400.
  • Response Body: Formats the errors into a map, typically mapping the field name to the error message, which is highly readable by API consumers.
import java.util.HashMap;
import java.util.Map;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;

@ControllerAdvice
public class CustomizedResponseEntityExceptionHandler {

    /**
     * Handles exceptions thrown when method argument validation fails (e.g., @Valid on @RequestBody).
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Map<String, String> handleValidationExceptions(MethodArgumentNotValidException ex) {

        Map<String, String> errors = new HashMap<>();

        ex.getBindingResult().getFieldErrors().forEach(error -> {
            errors.put(error.getField(), error.getDefaultMessage());
        });

        // Add Global Errors if any (non-field specific)
        ex.getBindingResult().getGlobalErrors().forEach(error -> {
            errors.put(error.getObjectName(), error.getDefaultMessage());
        });

        return errors;
    }
}

Testing Exception handler

The handler ensures a clean, consistent response when the input fails the defined constraints.

Example Client Request (Invalid Data)

If a client sends a request that violates the @Size and @Past constraints defined in the User bean:

{
    "name": "A",
    "birthDate": "2026-01-01"
}

Response (Processed by @ControllerAdvice)

Instead of default bulky HTTP 500 error, the client receives a structured 400 Bad Request with specific field issues:

{
    "name": "Name must be more than 2 characters.",
    "birthDate": "Birth date should be in past."
}

Request Flow

This approach centralizes error handling, adhering to the Separation of Concerns principle. The controller focuses purely on business logic, and the advice component handles the cross-cutting concern of exception management.

sequenceDiagram
    participant C as Client
    participant Ctrl as Controller (@PostMapping)
    participant EH as ExceptionHandler (@ControllerAdvice)

    C->>Ctrl: Invalid POST Request (User DTO)
    Ctrl->>Ctrl: Validation Triggered by @Valid
    alt Validation Fails
        Ctrl->>EH: throws MethodArgumentNotValidException
        EH->>EH: @ExceptionHandler catches Exception
        EH->>EH: Extracts Field Errors
        EH-->>C: 400 Bad Request (JSON Error Map)
    else Validation Success
        Ctrl->>Ctrl: Business Logic Executes
        Ctrl-->>C: 201 Created
    end

Note:
To standaradise the Error response across APIs, you can create a custom Error DTO (Data Transfer Object) instead of a simple Map<String, String> and include more contextual information, such as a timestamp, a unique error code, and the request path.


Best Practices

AreaSuggestionDetail
Error HandlingImplement a Global Exception Handler.Use @ControllerAdvice and @ExceptionHandler to uniformly catch MethodArgumentNotValidException and format the validation errors into a clean JSON response (e.g., listing all field errors) for better UX.
Group ValidationUse Validation Groups.For complex models, use groups (marker interfaces) to selectively apply validation constraints (e.g., different rules for Create vs. Update operations). Use @Validated(OnCreation.class) instead of just @Valid.
Custom ConstraintsCreate Custom Annotations.For business-specific validation (e.g., “Must be an existing product ID”), implement a custom constraint with @Constraint and a corresponding ConstraintValidator.
DRY PrincipleSeparate DTOs from Entities.Always validate a Data Transfer Object (DTO) that mirrors the request structure, not the JPA Entity itself, to keep persistence logic clean from presentation concerns.

Note:
Spring Boot internally uses the Hibernate Validator as the default implementation of the Jakarta Bean Validation API, where validation annotations are available in jakarta.validation.constraints.* package.