keepcalculator

06 Numbers and Powers

Spring Boot Validation for REST Services

Card No. 06·39575 · Updated · Standard formula, shown below

Spring Boot validation for REST services is the process of automatically checking incoming request data against defined constraints before it reaches your controller logic. You add annotations like @NotBlank or @Size to your DTO fields, and Spring Boot (with the spring-boot-starter-validation dependency) enforces them, returning a 400 Bad Request with a list of field errors when validation fails. This article explains how to set it up, common pitfalls, and how to customize error responses.

Why Validation Matters in REST APIs

REST services accept data from clients you don’t control. A missing email, a negative price, or a string 500 characters long can break your business logic or, worse, lead to database errors or security issues. Manual validation in every controller method leads to repetitive, error-prone code. Bean Validation (JSR-380) solves this by letting you declare rules on your data model once, and Spring Boot applies them automatically at the boundary of your application. This keeps your controllers clean, ensures consistent error messages, and catches invalid data before it touches your service layer.

Setting Up Spring Boot Validation

To use validation in a Spring Boot project, you need the validation starter. If you’re using Maven, add this to your pom.xml:

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

For Gradle, add implementation 'org.springframework.boot:spring-boot-starter-validation'. Since Spring Boot 2.3, the validation starter is no longer included in web starters, so you must add it explicitly. Once added, you can start annotating your DTOs.

Using Validation Annotations on DTOs

Create a request DTO (Data Transfer Object) and add constraints to its fields. Common annotations include:

  • @NotNull – the field must not be null.
  • @NotEmpty – the field must not be null and must have a length > 0 (for strings, collections, arrays).
  • @NotBlank – the string must not be null and must contain at least one non-whitespace character.
  • @Size(min=, max=) – limits the size of a string, collection, or array.
  • @Min(value) and @Max(value) – for numbers, the value must be >= or <= the given value.
  • @Email – checks that the string is a valid email address (allows null).
  • @Pattern(regexp=) – matches the string against a regular expression.
  • @Positive / @PositiveOrZero – number must be > 0 or >= 0.

Example DTO:

public class UserRegistrationRequest {
    @NotBlank(message = "Name is required")
    private String name;

    @Email(message = "Email should be valid")
    @NotBlank(message = "Email is required")
    private String email;

    @Min(value = 18, message = "Age must be at least 18")
    private int age;

    // getters and setters
}

Then in your controller, add @Valid (or @Validated) before the parameter:

@PostMapping("/users")
public ResponseEntity<User> createUser(@Valid @RequestBody UserRegistrationRequest request) {
    // ...
}

When validation fails, Spring throws MethodArgumentNotValidException and returns a 400 response with details.

Customizing the Error Response

By default, the error response is a JSON object with a timestamp, status, error, message, and path. Many APIs need a more structured or consistent format. You can handle the exception globally using @RestControllerAdvice:

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Map<String, String>> handleValidationExceptions(MethodArgumentNotValidException ex) {
        Map<String, String> errors = new HashMap<>();
        ex.getBindingResult().getFieldErrors().forEach(error -> 
            errors.put(error.getField(), error.getDefaultMessage()));
        return ResponseEntity.badRequest().body(errors);
    }
}

This returns a simple map like {"email":"Email should be valid"}. You can also include a list of all errors, or add a custom error code. For path variables and request parameters, use @Validated on the controller class and handle ConstraintViolationException similarly.

Validation Groups and Partial Updates

Sometimes you need different validation rules for different operations. For example, when creating a user, an ID is not required, but when updating, it is. Validation groups allow this:

public interface CreateGroup {}
public interface UpdateGroup {}

public class UserDto {
    @Null(groups = CreateGroup.class)
    @NotNull(groups = UpdateGroup.class)
    private Long id;

    @NotBlank(groups = {CreateGroup.class, UpdateGroup.class})
    private String name;
}

Then use @Validated(CreateGroup.class) in the controller method. This is especially useful for PATCH requests where you want to validate only the fields that are present. For partial updates, consider using a separate DTO with no @NotNull on optional fields, or use @Validated with groups and a custom check for null fields.

Common Pitfalls and How to Avoid Them

  • Forgetting the starter: Without spring-boot-starter-validation, @Valid does nothing. Always add it.
  • Not using @Valid on nested objects: If your DTO contains another object, you must annotate that field with @Valid to trigger validation of its constraints.
  • Using @NotBlank on non-strings: @NotBlank only works on CharSequence. Use @NotNull for numbers or booleans.
  • Ignoring @Validated vs @Valid: @Valid is for triggering validation on a parameter, while @Validated is a Spring annotation that also enables validation groups. Use @Valid on @RequestBody, and @Validated on the class for path/param validation.
  • Returning 500 instead of 400: If you don’t handle MethodArgumentNotValidException, Spring returns 400 by default. But if you have a global exception handler that catches all exceptions, it might turn it into a 500. Be careful with catch-all handlers.

Performance and Security Considerations

Validation happens on every request, which adds a small overhead. For most applications this is negligible. However, avoid complex regex patterns or database calls inside custom validators. For security, validation is your first line of defense against malformed input, but it’s not a substitute for proper sanitization and escaping when storing or displaying data. Also, be aware that @Email allows null by design; combine it with @NotBlank if the field is required.

Alternatives and Extensions

Bean Validation is the standard, but you can also use:

  • Manual validation in service layer: Useful for complex business rules that span multiple fields.
  • Spring’s Validator interface: For programmatic validation, but more verbose.
  • Custom annotations: Create your own constraint annotations for domain-specific rules, like checking if a date is in the future.
  • OpenAPI/JSON Schema validation: Tools like Swagger can validate requests, but they run outside the application, so they don’t protect your backend directly.

Frequently Asked Questions

Why is my validation not working?

Most likely you forgot to add the spring-boot-starter-validation dependency. In Spring Boot 2.3+, it’s not included by default. Also check that you have @Valid on the parameter and that your DTO fields have annotations.

How do I return field-specific error messages?

Use a @RestControllerAdvice with an @ExceptionHandler for MethodArgumentNotValidException. Iterate over getBindingResult().getFieldErrors() and build a map or list with field names and messages.

What’s the difference between @NotNull, @NotEmpty, and @NotBlank?

@NotNull checks that the value is not null (works on any object). @NotEmpty checks that a collection, array, or string is not null and has at least one element/character. @NotBlank is for strings only and requires at least one non-whitespace character.

Can I validate path variables and request parameters?

Yes. Add @Validated on the controller class and use annotations like @Min or @Pattern directly on the parameter. Handle ConstraintViolationException in your exception handler.

How do I validate a list of objects in the request body?

Use @Valid on the list parameter: public ResponseEntity<?> createUsers(@Valid @RequestBody List<@Valid UserDto> users). Note the @Valid on the generic type argument. Alternatively, wrap the list in a DTO with @Valid on the list field.

Does validation work with JPA entities?

Yes, you can put validation annotations on entity fields, and they will be enforced when you call save() if you have a Validator bean. However, it’s better practice to validate DTOs before mapping to entities, so you can return specific error messages without hitting the database.

Related

Method: the standard formula for this calculation, rounded as each result states. See how the calculators work or report an error.

More cards in this drawer

The whole Numbers and Powers drawer →