# Como integrar validação de CPF em aplicações Spring WebFlux (reativo)

> Aprenda a integrar a validação de CPF via API em aplicações Spring WebFlux usando WebClient reativo com exemplos completos de código.

**Publicado:** 21/01/2025
**Autor:** Redação CPFHub.io
**URL:** https://cpfhub.io/blog/como-integrar-validacao-de-cpf-em-aplicacoes-spring-webflux-reativo

---


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

```xml
<dependencies>
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-webflux</artifactId>
 </dependency>
</dependencies>
```

---

## Modelos de dados

```java
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:

```json
{
 "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:

```java
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

```java
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

```java
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:

```java
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

```bash
curl -X GET http://localhost:8080/api/cpf/12345678900 \
 -H "Accept: application/json" \
 --max-time 10
```

### application.properties

```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](https://docs.spring.io/spring-framework/reference/web/webflux.html) 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 `retryWhen` com backoff para lidar com falhas transitórias.

* **Não bloqueie** — Nunca use `.block()` em controllers WebFlux. Retorne sempre `Mono` ou `Flux`.

* **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.

### Leia também

- [Como consumir API de CPF em Java com Spring Boot e RestTemplate](https://cpfhub.io/blog/como-consumir-api-de-cpf-em-java-com-spring-boot-e-resttemplate)
- [Como usar RestTemplate e WebClient para consultar CPF via API em Java](https://cpfhub.io/blog/resttemplate-webclient-consultar-cpf-api-java)
- [Como implementar retry com backoff exponencial em consultas de API de CPF](https://cpfhub.io/blog/como-implementar-retry-backoff-exponencial-consultas-api-cpf)
- [Como criar um microsserviço de validação de CPF com Spring Boot e Docker](https://cpfhub.io/blog/microsservico-validacao-cpf-spring-boot-docker)

---

## 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](https://www.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.

