Validar CPF em Spring Boot: Bean Validation com @Constraint
Validação de CPF em Spring Boot usando Bean Validation com anotação personalizada @CpfValido. A lógica segue o algoritmo de módulo 11. Este artigo foca na integração com o framework.
Anotação
package com.example.validation;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.*;
@Documented
@Constraint(validatedBy = CpfValidator.class)
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface CpfValido {
String message() default "CPF inválido";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}ConstraintValidator
package com.example.validation;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
public class CpfValidator implements ConstraintValidator<CpfValido, String> {
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null || value.isBlank()) {
return true; // @NotBlank cuida de campos obrigatórios
}
String cpf = value.replaceAll("[^0-9]", "");
if (cpf.length() != 11) return false;
if (cpf.matches("(\\d)\\1{10}")) return false;
for (int t = 9; t < 11; t++) {
int sum = 0;
for (int c = 0; c < t; c++) {
sum += (cpf.charAt(c) - '0') * ((t + 1) - c);
}
int digit = ((10 * sum) % 11) % 10;
if ((cpf.charAt(t) - '0') != digit) return false;
}
return true;
}
}Análise do código
A anotação @CpfValido usa @Constraint(validatedBy = CpfValidator.class) para vincular a lógica de validação. @Target permite uso em campos e parâmetros de método.
No CpfValidator, isValid retorna true para valores nulos ou vazios. A responsabilidade de campo obrigatório fica com @NotBlank. Isso segue a convenção do Bean Validation onde cada anotação tem uma única responsabilidade.
replaceAll("[^0-9]", "") aceita CPF com ou sem máscara. matches("(\\d)\\1{10}") rejeita 11 dígitos iguais. O loop calcula ambos os dígitos verificadores usando a mesma lógica da implementação Java pura.
DTO com validação
package com.example.dto;
import com.example.validation.CpfValido;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
public record PessoaRequest(
@NotBlank String nome,
@NotBlank @CpfValido String cpf,
@NotBlank @Email String email
) {}Controller
package com.example.controller;
import com.example.dto.PessoaRequest;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/pessoas")
public class PessoaController {
@PostMapping
public ResponseEntity<?> criar(@Valid @RequestBody PessoaRequest request) {
// Se chegar aqui, o CPF já foi validado
return ResponseEntity.ok(request);
}
}Com @Valid, o Spring dispara a validação automaticamente e retorna 400 Bad Request com os erros se algum campo for inválido.
Resposta de erro
O Spring Boot retorna o erro de validação no formato padrão:
{
"timestamp": "2026-02-11T00:00:00.000+00:00",
"status": 400,
"errors": [
{
"field": "cpf",
"defaultMessage": "CPF inválido"
}
]
}Para personalizar o formato, implemente um @ControllerAdvice com @ExceptionHandler(MethodArgumentNotValidException.class).
Testes
package com.example.validation;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class CpfValidatorTest {
private CpfValidator validator;
@BeforeEach
void setUp() {
validator = new CpfValidator();
}
@Test
void cpfValidoComMascara() {
assertTrue(validator.isValid("529.982.247-25", null));
}
@Test
void cpfValidoSemMascara() {
assertTrue(validator.isValid("52998224725", null));
}
@Test
void cpfComDigitosRepetidos() {
assertFalse(validator.isValid("111.111.111-11", null));
}
@Test
void cpfComDigitoVerificadorIncorreto() {
assertFalse(validator.isValid("529.982.247-26", null));
}
@Test
void cpfComTamanhoIncorreto() {
assertFalse(validator.isValid("123.456.789", null));
}
@Test
void valorNuloRetornaTrue() {
assertTrue(validator.isValid(null, null));
}
@Test
void cpfComZerosAEsquerda() {
assertTrue(validator.isValid("000.000.001-91", null));
}
}Use o gerador de CPF válido para criar números de teste em lote.
Veja também: validar CPF em Java puro.