# Como Criar um Microsserviço de Validação de CPF com Spring Boot e Docker

> Aprenda a criar um microsserviço de validação de CPF com Spring Boot e Docker, com endpoints REST, health checks, métricas e containerização.

**Publicado:** 28/09/2024
**Autor:** Redação CPFHub.io
**URL:** https://cpfhub.io/blog/microsservico-validacao-cpf-spring-boot-docker

---


Para criar um microsserviço de validação de CPF com Spring Boot e Docker, estruture o projeto em camadas (controller, service, config, DTO), implemente a lógica de validação algorítmica localmente e delegue a consulta de dados à API CPFHub.io, depois containerize com multi-stage build para uma imagem de produção enxuta (~200MB). O Spring Actuator entrega health checks e métricas prontas sem configuração adicional.

## Introdução

Microsserviços são a arquitetura padrão para sistemas modernos, e um serviço dedicado à validação de CPF centraliza essa responsabilidade para toda a organização. O Spring Boot simplifica a criação do serviço, enquanto o Docker garante portabilidade e consistência entre ambientes.

## Estrutura do projeto

O projeto segue a estrutura padrão do Spring Boot com separação por camada.

```java
// Estrutura de diretórios
// cpf-service/
// src/main/java/com/cpfservice/
// CpfServiceApplication.java
// config/
// CpfHubConfig.java
// controller/
// CpfController.java
// HealthController.java
// service/
// CpfValidationService.java
// dto/
// CpfRequest.java
// CpfResponse.java
// exception/
// GlobalExceptionHandler.java
// src/main/resources/
// application.yml
// Dockerfile
// docker-compose.yml
```

| Camada | Responsabilidade | Pacote |
|---|---|---|
| Controller | Endpoints REST | controller |
| Service | Lógica de negócio | service |
| Config | Configuração de beans | config |
| DTO | Objetos de transferência | dto |
| Exception | Tratamento global de erros | exception |

---

## Configuração e DTOs

Configure a aplicação e defina os objetos de transferência de dados.

```java
// application.yml
// server:
// port: 8080
// cpfhub:
// api-key: ${CPFHUB_API_KEY}
// base-url: https://api.cpfhub.io
// timeout: 10000
// management:
// endpoints:
// web:
// exposure:
// include: health,metrics,info
// endpoint:
// health:
// show-details: always

// CpfHubConfig.java
package com.cpfservice.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.web.client.RestTemplate;
import java.time.Duration;

@Configuration
public class CpfHubConfig {

 @Value("${cpfhub.timeout:10000}")
 private int timeout;

 @Bean
 public RestTemplate restTemplate(RestTemplateBuilder builder) {
 return builder
 .setConnectTimeout(Duration.ofMillis(5000))
 .setReadTimeout(Duration.ofMillis(timeout))
 .build();
 }
}

// CpfRequest.java
package com.cpfservice.dto;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;

public record CpfRequest(
 @NotBlank(message = "CPF e obrigatorio")
 @Pattern(
 regexp = "\\d{11}|\\d{3}\\.\\d{3}\\.\\d{3}-\\d{2}",
 message = "CPF deve estar no formato correto"
 )
 String cpf
) {}

// CpfResponse.java
public record CpfResponse(
 boolean sucesso,
 String fonte,
 DadosCpf dados,
 String erro
) {
 public record DadosCpf(
 String cpf,
 String name,
 String nameUpper,
 String gender,
 String birthDate,
 int day,
 int month,
 int year
 ) {}
}
```

---

## Service layer

O service implementa a lógica de validação algorítmica e consulta via API.

```java
package com.cpfservice.service;

import com.cpfservice.dto.CpfResponse;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Service
public class CpfValidationService {

 private final RestTemplate restTemplate;
 private final String apiKey;
 private final String baseUrl;
 private final ObjectMapper mapper;

 public CpfValidationService(
 RestTemplate restTemplate,
 @Value("${cpfhub.api-key}") String apiKey,
 @Value("${cpfhub.base-url}") String baseUrl) {
 this.restTemplate = restTemplate;
 this.apiKey = apiKey;
 this.baseUrl = baseUrl;
 this.mapper = new ObjectMapper();
 }

 public CpfResponse validar(String cpf) {
 String cpfLimpo = cpf.replaceAll("\\D", "");

 // Validação algorítmica
 if (!validarAlgoritmo(cpfLimpo)) {
 return new CpfResponse(
 false, "algoritmo", null,
 "CPF invalido (digitos verificadores)"
 );
 }

 // Consulta via API
 try {
 HttpHeaders headers = new HttpHeaders();
 headers.set("x-api-key", apiKey);

 ResponseEntity<String> response =
 restTemplate.exchange(
 baseUrl + "/cpf/" + cpfLimpo,
 HttpMethod.GET,
 new HttpEntity<>(headers),
 String.class
 );

 JsonNode root = mapper.readTree(response.getBody());

 if (root.get("success").asBoolean()) {
 JsonNode data = root.get("data");
 CpfResponse.DadosCpf dados =
 new CpfResponse.DadosCpf(
 data.get("cpf").asText(),
 data.get("name").asText(),
 data.get("nameUpper").asText(),
 data.get("gender").asText(),
 data.get("birthDate").asText(),
 data.get("day").asInt(),
 data.get("month").asInt(),
 data.get("year").asInt()
 );
 return new CpfResponse(true, "api", dados, null);
 }

 return new CpfResponse(
 false, "api", null, "CPF nao encontrado"
 );
 } catch (Exception e) {
 return new CpfResponse(
 false, "erro", null,
 "Erro na consulta: " + e.getMessage()
 );
 }
 }

 private boolean validarAlgoritmo(String cpf) {
 if (cpf.length() != 11) return false;
 if (cpf.chars().distinct().count() == 1) return false;

 int soma = 0;
 for (int i = 0; i < 9; i++) {
 soma += Character.getNumericValue(cpf.charAt(i))
 * (10 - i);
 }
 int resto = (soma * 10) % 11;
 if (resto == 10) resto = 0;
 if (resto != Character.getNumericValue(cpf.charAt(9)))
 return false;

 soma = 0;
 for (int i = 0; i < 10; i++) {
 soma += Character.getNumericValue(cpf.charAt(i))
 * (11 - i);
 }
 resto = (soma * 10) % 11;
 if (resto == 10) resto = 0;
 return resto == Character.getNumericValue(cpf.charAt(10));
 }
}
```

---

## Controller e exception handler

O controller expõe os endpoints REST para validação de CPF.

```java
package com.cpfservice.controller;

import com.cpfservice.dto.CpfResponse;
import com.cpfservice.service.CpfValidationService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/api/v1/cpf")
public class CpfController {

 private final CpfValidationService service;

 public CpfController(CpfValidationService service) {
 this.service = service;
 }

 @GetMapping("/{cpf}")
 public ResponseEntity<CpfResponse> validar(
 @PathVariable String cpf) {
 CpfResponse resultado = service.validar(cpf);
 int status = resultado.sucesso() ? 200 : 404;
 return ResponseEntity.status(status).body(resultado);
 }

 @PostMapping("/lote")
 public ResponseEntity<Map<String, Object>> validarLote(
 @RequestBody List<String> cpfs) {
 List<CpfResponse> resultados = cpfs.stream()
 .map(service::validar)
 .toList();

 long validos = resultados.stream()
 .filter(CpfResponse::sucesso).count();

 return ResponseEntity.ok(Map.of(
 "total", cpfs.size(),
 "validos", validos,
 "invalidos", cpfs.size() - validos,
 "resultados", resultados
 ));
 }
}
```

| Endpoint | Método | Descrição |
|---|---|---|
| `/api/v1/cpf/{cpf}` | GET | Validar CPF individual |
| `/api/v1/cpf/lote` | POST | Validar lote de CPFs |
| `/actuator/health` | GET | Health check |
| `/actuator/metrics` | GET | Métricas da aplicação |

---

## Dockerfile e Docker Compose

Containerize o serviço com multi-stage build para uma imagem otimizada. A [documentação oficial do Docker](https://docs.docker.com/build/building/multi-stage/) recomenda essa abordagem para reduzir o tamanho da imagem final em aplicações JVM.

```dockerfile
# Dockerfile
FROM eclipse-temurin:21-jdk-alpine AS builder
WORKDIR /app
COPY . .
RUN ./mvnw clean package -DskipTests

FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY --from=builder /app/target/*.jar app.jar

EXPOSE 8080

HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
 CMD wget -qO- http://localhost:8080/actuator/health || exit 1

ENTRYPOINT ["java", "-jar", "app.jar"]
```

```yaml
# docker-compose.yml
version: "3.8"

services:
 cpf-service:
 build: .
 ports:
 - "8080:8080"
 environment:
 - CPFHUB_API_KEY=${CPFHUB_API_KEY}
 - SPRING_PROFILES_ACTIVE=prod
 - JAVA_OPTS=-Xmx256m -Xms128m
 healthcheck:
 test: ["CMD", "wget", "-qO-", "http://localhost:8080/actuator/health"]
 interval: 30s
 timeout: 3s
 retries: 3
 restart: unless-stopped
 deploy:
 resources:
 limits:
 memory: 512M
 cpus: "0.5"
```

| Estágio | Imagem | Tamanho |
|---|---|---|
| Builder | eclipse-temurin:21-jdk-alpine | ~400MB |
| Runtime | eclipse-temurin:21-jre-alpine | ~180MB |
| Imagem final | Com aplicação | ~200MB |

---

## Perguntas frequentes

### Por que usar um microsserviço dedicado em vez de validar o CPF diretamente em cada serviço?
Centralizar a validação em um microsserviço elimina duplicação de código, garante consistência nas regras de negócio e facilita auditoria. Quando as regras mudam (novo provider de API, lógica de cache, requisitos de PLD/FT), a atualização ocorre em um único ponto sem impactar os consumidores.

### Como o Spring Actuator ajuda em produção?
O Actuator expõe endpoints `/actuator/health` e `/actuator/metrics` que orquestradores como Kubernetes e balanceadores de carga usam para saber se o serviço está saudável. O health check configurado no Dockerfile e no docker-compose.yml reinicia o container automaticamente em caso de falha.

### Como lidar com timeouts nas chamadas à API CPFHub.io?
Configure `setConnectTimeout` (recomendado: 5s) e `setReadTimeout` (recomendado: 10s) no `RestTemplateBuilder`. A latência típica da CPFHub.io é ~900ms, então um timeout de 10s cobre picos ocasionais sem prejudicar a experiência. Em caso de timeout, retorne um erro controlado em vez de deixar a thread travar.

### A API CPFHub.io bloqueia requisições se o limite do plano for atingido?
Não. Se o volume de consultas ultrapassar o limite do plano contratado, a API continua respondendo normalmente e cobra R$0,15 por consulta adicional. Isso garante que o microsserviço nunca perca disponibilidade por causa de cotas — apenas revise o faturamento em `app.cpfhub.io/settings/billing`.

### Leia também

- [Diferença entre validação de CPF e consulta de CPF: quando usar cada uma](https://cpfhub.io/blog/diferenca-entre-validacao-de-cpf-e-consulta-de-cpf-quando-usar-cada-uma)
- [API de CPF grátis para desenvolvedores: como começar em 5 minutos](https://cpfhub.io/blog/api-cpf-gratis-desenvolvedores-comecar-5-minutos)
- [Onboarding digital em fintechs: como validar CPF em menos de 30 segundos](https://cpfhub.io/blog/onboarding-digital-em-fintechs-como-validar-cpf-em-menos-de-30-segundos)
- [KYC no Brasil: quais setores são obrigados a validar CPF por lei](https://cpfhub.io/blog/kyc-no-brasil-quais-setores-sao-obrigados-a-validar-cpf-por-lei)

---

## Conclusão

Um microsserviço de validação de CPF com Spring Boot e Docker centraliza a responsabilidade de validação, oferecendo endpoints REST consistentes para toda a organização. O Spring Actuator fornece health checks e métricas prontas para produção, enquanto o Docker garante portabilidade e consistência entre ambientes. Essa arquitetura permite escalar o serviço independentemente e atualizá-lo sem impactar os consumidores.

Cadastre-se em [cpfhub.io](https://www.cpfhub.io/) — 50 consultas mensais gratuitas, sem cartão de crédito — e integre a API CPFHub.io ao seu microsserviço Spring Boot em menos de 30 minutos.

