DTOs and request validation

Spring Boot · lesson 10 of 39 · 4 min read

Separate wire models from entities and reject bad input before it reaches your logic.

Open this lesson in the learning hub

Key points

  • Never expose a JPA entity as your API. A DTO lets the database and the contract change independently.
  • Java records are ideal DTOs: immutable, tiny, and Jackson binds them via the constructor.
  • Annotate the request record with Bean Validation constraints, then add @Valid at the parameter.
  • A failed check throws MethodArgumentNotValidException and Boot answers 400, before your code runs.
  • Validate nested objects too — put @Valid on the nested field, or validation stops at the surface.
  • Requests and responses are different shapes. A create request has no id; a response does.

Example

import java.util.List;
import jakarta.validation.Valid;
import jakarta.validation.constraints.*;

public record CreateOrderRequest(
        @NotBlank @Email String customerEmail,
        @NotEmpty @Valid List<LineItem> items,          // @Valid = check each item
        @Positive BigDecimal total) {

    public record LineItem(
            @NotBlank String sku,
            @Min(1) @Max(99) int quantity) {}
}

public record OrderResponse(long id, String status, BigDecimal total) {}

@PostMapping
public ResponseEntity<OrderResponse> create(@Valid @RequestBody CreateOrderRequest req) {
    // If we get here, req is already valid.
    return ResponseEntity.ok(service.create(req));
}

DTOs protect your contract; @Valid stops bad data at the door.

This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Spring Boot course, and every lesson in it is listed on the Spring Boot contents page.