Integrar validação de CPF em aplicações Spring WebFlux significa usar o WebClient reativo para chamar a API CPFHub.io de forma não bloqueante: a thread do servidor fica livre enquanto aguarda a resposta, mantendo alta eficiência mesmo sob grandes volumes de requisições simultâneas.
Introdução
O Spring WebFlux é o módulo reativo do Spring Framework, projetado para construir aplicações assíncronas e não bloqueantes que podem lidar com alto volume de requisições simultâneas. Para empresas que processam milhares de cadastros, onboardings ou transações por minuto, a abordagem reativa é ideal para manter a eficiência sem desperdiçar recursos do servidor.
A validação de CPF via API é uma operação de I/O (rede) que se beneficia diretamente do modelo reativo: enquanto a resposta da API externa é aguardada, a thread do servidor fica livre para processar outras requisições.
Pré-requisitos
- Java 17+ — Versão LTS recomendada.
- Spring Boot 3.x com WebFlux — Dependência
spring-boot-starter-webflux. - Conta na CPFHub.io — Para obter a chave de API (50 consultas/mês no plano gratuito).
Dependências Maven
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
</dependencies>
Modelos de dados
import com.fasterxml.jackson.annotation.JsonProperty;
public record CpfResponse(
@JsonProperty("success") boolean success,
@JsonProperty("data") CpfData data
) {}
public record CpfData(
@JsonProperty("cpf") String cpf,
@JsonProperty("name") String name,
@JsonProperty("nameUpper") String nameUpper,
@JsonProperty("gender") String gender,
@JsonProperty("birthDate") String birthDate,
@JsonProperty("day") int day,
@JsonProperty("month") int month,
@JsonProperty("year") int year
) {}
Resposta esperada da API:
{
"success": true,
"data": {
"cpf": "12345678900",
"name": "João da Silva",
"nameUpper": "JOÃO DA SILVA",
"gender": "M",
"birthDate": "15/06/1990",
"day": 15,
"month": 6,
"year": 1990
}
}
Configurando o WebClient
Crie um Bean de WebClient com timeout e headers padrão:
import io.netty.channel.ChannelOption;
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.handler.timeout.WriteTimeoutHandler;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.netty.http.client.HttpClient;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
@Configuration
public class WebClientConfig {
@Value("${cpfhub.api-key}")
private String apiKey;
@Value("${cpfhub.base-url:https://api.cpfhub.io}")
private String baseUrl;
@Bean
public WebClient cpfHubWebClient() {
HttpClient httpClient = HttpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
.responseTimeout(Duration.ofSeconds(10))
.doOnConnected(conn -> conn
.addHandlerLast(new ReadTimeoutHandler(10, TimeUnit.SECONDS))
.addHandlerLast(new WriteTimeoutHandler(10, TimeUnit.SECONDS))
);
return WebClient.builder()
.baseUrl(baseUrl)
.clientConnector(new ReactorClientHttpConnector(httpClient))
.defaultHeader("x-api-key", apiKey)
.defaultHeader(HttpHeaders.ACCEPT,
MediaType.APPLICATION_JSON_VALUE)
.build();
}
}
Criando o serviço reativo
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatusCode;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
@Service
public class CpfValidationService {
private static final Logger log = LoggerFactory.getLogger(
CpfValidationService.class);
private final WebClient webClient;
public CpfValidationService(WebClient cpfHubWebClient) {
this.webClient = cpfHubWebClient;
}
public Mono<CpfData> validarCpf(String cpf) {
String cpfLimpo = cpf.replaceAll("\\D", "");
if (cpfLimpo.length() != 11) {
return Mono.error(new IllegalArgumentException(
"CPF inválido. Informe 11 dígitos."));
}
return webClient.get()
.uri("/cpf/{cpf}", cpfLimpo)
.retrieve()
.onStatus(
HttpStatusCode::isError,
response -> Mono.error(new RuntimeException(
"Erro HTTP: " + response.statusCode()))
)
.bodyToMono(CpfResponse.class)
.flatMap(response -> {
if (response.success() && response.data() != null) {
log.info("CPF {} validado com sucesso.", cpfLimpo);
return Mono.just(response.data());
}
log.warn("CPF {} não encontrado.", cpfLimpo);
return Mono.empty();
})
.timeout(Duration.ofSeconds(10))
.doOnError(e -> log.error(
"Erro ao consultar CPF {}: {}", cpfLimpo, e.getMessage()));
}
}
Observe que toda a cadeia é reativa: nenhuma thread é bloqueada enquanto aguarda a resposta da API.
Criando o Controller reativo
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Mono;
import java.util.Map;
@RestController
@RequestMapping("/api/cpf")
public class CpfController {
private final CpfValidationService cpfService;
public CpfController(CpfValidationService cpfService) {
this.cpfService = cpfService;
}
@GetMapping("/{cpf}")
public Mono<ResponseEntity<Map<String, Object>>> validar(
@PathVariable String cpf) {
return cpfService.validarCpf(cpf)
.map(dados -> ResponseEntity.ok(Map.<String, Object>of(
"valido", true,
"dados", dados
)))
.defaultIfEmpty(ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(Map.of(
"valido", false,
"erro", "CPF não encontrado."
)))
.onErrorResume(IllegalArgumentException.class, e ->
Mono.just(ResponseEntity.badRequest().body(Map.of(
"erro", e.getMessage()
)))
)
.onErrorResume(e ->
Mono.just(ResponseEntity.status(
HttpStatus.INTERNAL_SERVER_ERROR).body(Map.of(
"erro", e.getMessage()
)))
);
}
}
Implementando retry com backoff
Para maior resiliência, adicione retry com backoff exponencial ao serviço:
import java.time.Duration;
import reactor.util.retry.Retry;
public Mono<CpfData> validarCpfComRetry(String cpf) {
return validarCpf(cpf)
.retryWhen(Retry.backoff(3, Duration.ofSeconds(2))
.filter(throwable ->
!(throwable instanceof IllegalArgumentException))
.doBeforeRetry(signal ->
log.warn("Tentativa {} de consulta de CPF.",
signal.totalRetries() + 1))
);
}
Testando a aplicação
curl -X GET http://localhost:8080/api/cpf/12345678900 \
-H "Accept: application/json" \
--max-time 10
application.properties
cpfhub.api-key=SUA_CHAVE_DE_API
cpfhub.base-url=https://api.cpfhub.io
WebFlux vs. RestTemplate
| Característica | RestTemplate | WebClient (WebFlux) |
|---|---|---|
| Modelo | Bloqueante | Reativo (não bloqueante) |
| Threads | Uma thread por requisição | Poucas threads para muitas requisições |
| Uso de recursos | Alto em alto volume | Eficiente em alto volume |
| Status | Em manutenção | Recomendado pelo Spring |
| Complexidade | Menor | Maior (exige conhecimento de Reactor) |
Para aplicações com alto throughput, o WebFlux é a escolha recomendada. A documentação oficial do Spring detalha os casos de uso e as diferenças de modelo entre os dois módulos.
Boas práticas
-
Timeout — Configure timeout no Netty HttpClient e no operador
.timeout(). -
Retry — Use
retryWhencom backoff para lidar com falhas transitórias. -
Não bloqueie — Nunca use
.block()em controllers WebFlux. Retorne sempreMonoouFlux. -
Plano gratuito — O plano gratuito da CPFHub.io oferece 50 consultas/mês sem cartão de crédito. O plano Pro oferece 1.000 consultas por R$149/mês. Ao superar o limite, a API não bloqueia: cobra R$0,15 por consulta adicional.
Perguntas frequentes
Por que usar WebClient em vez de RestTemplate para chamar a API de CPF?
O WebClient é a abordagem reativa: a thread não fica bloqueada aguardando a resposta da API, liberando recursos para outras requisições. Em aplicações com alto volume de validações simultâneas (onboarding em massa, por exemplo), o ganho de eficiência é significativo. O RestTemplate está em modo de manutenção no Spring e não é recomendado para novos projetos.
Como evitar que o .block() cause deadlock em aplicações WebFlux?
Nunca chame .block() dentro de um contexto reativo — controllers, services ou handlers WebFlux. Se precisar de um valor síncrono, reestruture o código para retornar Mono ou Flux até o ponto de saída. Para testes, use StepVerifier da biblioteca Reactor Test em vez de .block().
Qual timeout configurar para a chamada à API de CPF no WebClient?
Configure pelo menos dois timeouts: connectTimeout (5 segundos) no HttpClient do Netty e responseTimeout (10 segundos) no operador .timeout() do Reactor. A API CPFHub.io tem latência típica de ~900ms; o timeout de 10 segundos é conservador o suficiente para absorver picos sem prejudicar a experiência do usuário.
Como implementar cache reativo para evitar consultas repetidas ao mesmo CPF?
Use Mono.cache() ou integre com Spring Cache via anotação @Cacheable em conjunto com um ReactiveRedisTemplate. O cache reativo evita chamadas desnecessárias para o mesmo CPF em janelas curtas de tempo, reduzindo o consumo mensal de consultas sem comprometer a qualidade dos dados.
Conclusão
Integrar a validação de CPF em aplicações Spring WebFlux usando WebClient é a abordagem ideal para sistemas que precisam lidar com alto volume de requisições de forma eficiente. Com o modelo reativo, cada consulta à API CPFHub.io ocorre sem bloquear threads, resultando em menor consumo de memória e maior throughput sob carga.
Cadastre-se em cpfhub.io — 50 consultas mensais gratuitas, sem cartão de crédito — e implemente a validação reativa de CPF no seu projeto Spring WebFlux ainda hoje.
CPFHub.io
Pronto para integrar a API?
50 consultas gratuitas para testar agora. Sem cartão de crédito. Acesso imediato à documentação.
Sobre a redação
Redação CPFHub.io
Time editorial especializado em APIs de CPF, identidade digital e compliance no mercado brasileiro. Produzimos guias técnicos, análises regulatórias e tutoriais sobre LGPD e KYC para desenvolvedores e líderes de produto.



