O Gerador De CPF O Gerador De CPF

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.