Pular para o conteúdo principal

Capítulo 2: Validação de Dados

Introdução​

A validação de dados é essencial para garantir a integridade e a qualidade dos dados na sua aplicação. O Spring Boot integra o Bean Validation (JSR 380) para validar facilmente os dados de entrada.


Bean Validation (JSR 380)​

Dependência Maven​

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

Nota: Esta dependência já está incluída em spring-boot-starter-web.


Principais Anotações de Validação​

Tabela Resumo​

AnotaçãoUsoExemplo
@NotNullNão pode ser null@NotNull private String name;
@NotEmptyNão pode ser null ou vazio@NotEmpty private String email;
@NotBlankNão pode ser null, vazio ou só espaços@NotBlank private String title;
@SizeTamanho min/máx@Size(min=3, max=50) private String name;
@MinValor mínimo@Min(0) private int age;
@MaxValor máximo@Max(100) private int grade;
@EmailFormato de e-mail válido@Email private String email;
@PatternExpressão regular@Pattern(regexp="[0-9]{10}") private String phone;
@PastData no passado@Past private LocalDate birthDate;
@FutureData no futuro@Future private LocalDate appointmentDate;

Exemplo Completo: Entidade com Validação​

package com.example.demo.model;

import jakarta.persistence.*;
import jakarta.validation.constraints.*;

@Entity
@Table(name = "products")
public class Product {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@NotBlank(message = "O nome não pode ser vazio")
@Size(min = 3, max = 50, message = "O nome deve ter entre 3 e 50 caracteres")
private String name;

@NotNull(message = "O preço é obrigatório")
@Min(value = 0, message = "O preço não pode ser negativo")
private Double price;

@NotNull(message = "A quantidade é obrigatória")
@Min(value = 0, message = "A quantidade não pode ser negativa")
private Integer quantity;

@Email(message = "E-mail inválido")
private String supplierEmail;

// Construtores, getters, setters
public Product() {}

public Product(String name, Double price, Integer quantity) {
this.name = name;
this.price = price;
this.quantity = quantity;
}

// Getters e Setters
public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Double getPrice() {
return price;
}

public void setPrice(Double price) {
this.price = price;
}

public Integer getQuantity() {
return quantity;
}

public void setQuantity(Integer quantity) {
this.quantity = quantity;
}

public String getSupplierEmail() {
return supplierEmail;
}

public void setSupplierEmail(String supplierEmail) {
this.supplierEmail = supplierEmail;
}
}

Uso no Controller com @Valid​

package com.example.demo.controller;

import com.example.demo.model.Product;
import com.example.demo.service.ProductService;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/products")
public class ProductController {

private final ProductService productService;

public ProductController(ProductService productService) {
this.productService = productService;
}

// CREATE com validação
@PostMapping
public ResponseEntity<Product> createProduct(@Valid @RequestBody Product product) {
Product created = productService.createProduct(product);
return ResponseEntity.status(HttpStatus.CREATED).body(created);
}

// UPDATE com validação
@PutMapping("/{id}")
public ResponseEntity<Product> updateProduct(@PathVariable Long id,
@Valid @RequestBody Product product) {
Product updated = productService.updateProduct(id, product);
return ResponseEntity.ok(updated);
}

// GET - Todos
@GetMapping
public ResponseEntity<List<Product>> getAllProducts() {
return ResponseEntity.ok(productService.getAllProducts());
}

// GET - Por ID
@GetMapping("/{id}")
public ResponseEntity<Product> getProductById(@PathVariable Long id) {
Product product = productService.getProductById(id);
return ResponseEntity.ok(product);
}

// DELETE
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteProduct(@PathVariable Long id) {
productService.deleteProduct(id);
return ResponseEntity.noContent().build();
}
}

Pontos-chave:

  • @Valid dispara a validação automática
  • Se a validação falhar, o Spring retorna automaticamente um código 400 Bad Request

Tratamento de Erros de Validação​

Resposta Automática (padrão)​

Quando a validação falha, o Spring retorna automaticamente:

{
"timestamp": "2025-11-28T10:15:30.000+00:00",
"status": 400,
"error": "Bad Request",
"message": "Validation failed",
"errors": [
{
"field": "name",
"message": "O nome não pode ser vazio"
},
{
"field": "price",
"message": "O preço não pode ser negativo"
}
]
}

Personalização com @ControllerAdvice​

package com.example.demo.exception;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

import java.util.HashMap;
import java.util.Map;

@ControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handleValidationExceptions(
MethodArgumentNotValidException ex) {

Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach((error) -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});

return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errors);
}
}

Resposta personalizada:

{
"name": "O nome não pode ser vazio",
"price": "O preço não pode ser negativo"
}

Mensagens Personalizadas​

Abordagem 1: Diretamente na anotação​

@NotBlank(message = "O nome do produto é obrigatório")
@Size(min = 3, max = 50, message = "O nome deve ter entre {min} e {max} caracteres")
private String name;

Abordagem 2: Arquivo de mensagens (messages.properties)​

Arquivo src/main/resources/messages.properties:

product.name.notblank=O nome do produto é obrigatório
product.name.size=O nome deve ter entre {min} e {max} caracteres
product.price.min=O preço não pode ser menor que {value}

Entidade:

@NotBlank(message = "{product.name.notblank}")
@Size(min = 3, max = 50, message = "{product.name.size}")
private String name;

@Min(value = 0, message = "{product.price.min}")
private Double price;

Validação Personalizada​

Criar uma Anotação Personalizada​

package com.example.demo.validation;

import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.*;

@Documented
@Constraint(validatedBy = PhoneNumberValidator.class)
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidPhoneNumber {
String message() default "Número de telefone inválido";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

Criar o Validador​

package com.example.demo.validation;

import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;

public class PhoneNumberValidator implements ConstraintValidator<ValidPhoneNumber, String> {

@Override
public void initialize(ValidPhoneNumber constraintAnnotation) {
}

@Override
public boolean isValid(String phoneNumber, ConstraintValidatorContext context) {
if (phoneNumber == null) {
return false;
}
// Formato canadense: (XXX) XXX-XXXX ou XXX-XXX-XXXX
return phoneNumber.matches("\\(?\\d{3}\\)?[- ]?\\d{3}[- ]?\\d{4}");
}
}

Utilização​

@Entity
public class Customer {
@Id
@GeneratedValue
private Long id;

@ValidPhoneNumber(message = "O número de telefone deve estar no formato canadense")
private String phoneNumber;

// Getters, setters
}

Grupos de Validação​

Permite validar de forma diferente conforme o contexto (criação vs atualização).

public interface CreateValidation {}
public interface UpdateValidation {}

@Entity
public class Product {
@Id
@GeneratedValue
@Null(groups = CreateValidation.class, message = "O ID deve ser null na criação")
@NotNull(groups = UpdateValidation.class, message = "O ID é obrigatório para atualização")
private Long id;

@NotBlank(groups = {CreateValidation.class, UpdateValidation.class})
private String name;
}

// Controller
@PostMapping
public ResponseEntity<Product> create(@Validated(CreateValidation.class) @RequestBody Product product) {
// ...
}

@PutMapping("/{id}")
public ResponseEntity<Product> update(@PathVariable Long id,
@Validated(UpdateValidation.class) @RequestBody Product product) {
// ...
}

Resumo do Capítulo​

Pontos-chave​

  • Bean Validation (JSR 380) para validar dados
  • @Valid no controller para disparar a validação
  • Mensagens personalizadas nas anotações ou arquivos properties
  • @ControllerAdvice para tratar erros globalmente
  • Validação personalizada com anotações customizadas

Anotações Essenciais​

✅ @NotNull, @NotEmpty, @NotBlank
✅ @Size, @Min, @Max
✅ @Email, @Pattern
✅ @Past, @Future
✅ @Valid


Próximo capítulo: Tratamento de Erros HTTP

Voltar ao Módulo 3