# Como Processar Consultas de CPF em Lote Usando Java Streams e CompletableFuture

> Aprenda a processar consultas de CPF em lote usando Java Streams e CompletableFuture, com concorrência controlada e agregação de resultados.

**Publicado:** 22/09/2024
**Autor:** Redação CPFHub.io
**URL:** https://cpfhub.io/blog/processar-consultas-cpf-lote-java-streams-completablefuture

---


Para processar consultas de CPF em lote com Java, combine Streams para o pipeline declarativo com `CompletableFuture` e um `ExecutorService` de tamanho fixo para controlar a concorrência. Com 10 a 20 threads paralelas, um lote de 1.000 CPFs consultado via [CPFHub.io](https://www.cpfhub.io/) é concluído em cerca de 20 segundos — contra mais de 3 minutos em processamento sequencial.

## Introdução

O processamento em lote de CPFs é necessário em diversas operações: migração de bases de clientes, validação periódica de cadastros, compliance regulatório e onboarding em massa. O Java oferece duas ferramentas poderosas para esse cenário: Streams para processamento declarativo e CompletableFuture para concorrência assíncrona.

---

## Processamento sequencial vs. paralelo

A diferença de performance entre abordagens sequenciais e paralelas é significativa em lotes grandes.

| Abordagem | 100 CPFs | 1.000 CPFs | 10.000 CPFs |
|---|---|---|---|
| Stream sequencial | ~20s | ~200s | ~2000s |
| Parallel stream | ~4s | ~40s | ~400s |
| CompletableFuture (10 conc.) | ~2s | ~20s | ~200s |
| CompletableFuture (50 conc.) | ~0.4s | ~4s | ~40s |

**Stream sequencial** -- simples, mas lento para lotes grandes.

**Parallel stream** -- usa o ForkJoinPool comum, limitado e compartilhado com outras operações.

**CompletableFuture** -- controle total sobre concorrência com executor customizado.

---

## Cliente HTTP base

O cliente HTTP que será usado por todas as abordagens.

```java
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

public class CpfApiClient {

 private final HttpClient httpClient;
 private final String apiKey;
 private final ObjectMapper mapper;

 public CpfApiClient(String apiKey) {
 this.httpClient = HttpClient.newBuilder()
 .connectTimeout(Duration.ofSeconds(5))
 .build();
 this.apiKey = apiKey;
 this.mapper = new ObjectMapper();
 }

 public ResultadoCpf consultar(String cpf) {
 try {
 HttpRequest request = HttpRequest.newBuilder()
 .uri(URI.create(
 "https://api.cpfhub.io/cpf/" + cpf
 ))
 .header("x-api-key", apiKey)
 .timeout(Duration.ofSeconds(10))
 .GET()
 .build();

 HttpResponse<String> response = httpClient.send(
 request,
 HttpResponse.BodyHandlers.ofString()
 );

 JsonNode root = mapper.readTree(response.body());
 if (root.get("success").asBoolean()) {
 JsonNode data = root.get("data");
 return new ResultadoCpf(
 cpf, true,
 data.get("name").asText(),
 data.get("gender").asText(),
 data.get("birthDate").asText(),
 null
 );
 }
 return new ResultadoCpf(
 cpf, false, null, null, null,
 "Nao encontrado"
 );
 } catch (Exception e) {
 return new ResultadoCpf(
 cpf, false, null, null, null,
 e.getMessage()
 );
 }
 }
}

public record ResultadoCpf(
 String cpf,
 boolean sucesso,
 String nome,
 String genero,
 String dataNascimento,
 String erro
) {}
```

---

## Processamento com Streams

A Stream API permite processamento declarativo e funcional.

```java
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class CpfBatchProcessor {

 private final CpfApiClient apiClient;

 public CpfBatchProcessor(String apiKey) {
 this.apiClient = new CpfApiClient(apiKey);
 }

 // Processamento sequencial com Stream
 public List<ResultadoCpf> processarSequencial(List<String> cpfs) {
 return cpfs.stream()
 .map(cpf -> cpf.replaceAll("\\D", ""))
 .filter(cpf -> cpf.length() == 11)
 .map(apiClient::consultar)
 .toList();
 }

 // Processamento paralelo com Parallel Stream
 public List<ResultadoCpf> processarParalelo(List<String> cpfs) {
 return cpfs.parallelStream()
 .map(cpf -> cpf.replaceAll("\\D", ""))
 .filter(cpf -> cpf.length() == 11)
 .map(apiClient::consultar)
 .toList();
 }

 // Agregação de resultados
 public Map<Boolean, List<ResultadoCpf>> processarEAgrupar(
 List<String> cpfs) {
 return cpfs.parallelStream()
 .map(cpf -> cpf.replaceAll("\\D", ""))
 .filter(cpf -> cpf.length() == 11)
 .map(apiClient::consultar)
 .collect(Collectors.partitioningBy(
 ResultadoCpf::sucesso
 ));
 }
}
```

---

## CompletableFuture com concorrência controlada

O CompletableFuture permite controle preciso sobre a concorrência.

```java
import java.util.List;
import java.util.concurrent.*;

public class CpfAsyncBatchProcessor {

 private final CpfApiClient apiClient;
 private final ExecutorService executor;

 public CpfAsyncBatchProcessor(
 String apiKey, int concorrencia) {
 this.apiClient = new CpfApiClient(apiKey);
 this.executor = Executors.newFixedThreadPool(concorrencia);
 }

 public List<ResultadoCpf> processarAsync(
 List<String> cpfs) {

 List<CompletableFuture<ResultadoCpf>> futures =
 cpfs.stream()
 .map(cpf -> cpf.replaceAll("\\D", ""))
 .filter(cpf -> cpf.length() == 11)
 .map(cpf -> CompletableFuture.supplyAsync(
 () -> apiClient.consultar(cpf),
 executor
 ))
 .toList();

 return futures.stream()
 .map(CompletableFuture::join)
 .toList();
 }

 public CompletableFuture<List<ResultadoCpf>> processarAsyncNaoBloqueante(
 List<String> cpfs) {

 List<CompletableFuture<ResultadoCpf>> futures =
 cpfs.stream()
 .map(cpf -> cpf.replaceAll("\\D", ""))
 .filter(cpf -> cpf.length() == 11)
 .map(cpf -> CompletableFuture.supplyAsync(
 () -> apiClient.consultar(cpf),
 executor
 ))
 .toList();

 CompletableFuture<Void> allOf =
 CompletableFuture.allOf(
 futures.toArray(new CompletableFuture[0])
 );

 return allOf.thenApply(v ->
 futures.stream()
 .map(CompletableFuture::join)
 .toList()
 );
 }

 public void shutdown() {
 executor.shutdown();
 try {
 if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
 executor.shutdownNow();
 }
 } catch (InterruptedException e) {
 executor.shutdownNow();
 Thread.currentThread().interrupt();
 }
 }
}
```

| Parâmetro | Valor Recomendado | Justificativa |
|---|---|---|
| Pool size | 10-20 | Equilibra concorrência e rate limit |
| Timeout por request | 10s | Evita threads travadas |
| Batch size | 100-500 | Evita sobrecarga de memória |
| Delay entre batches | 1-5s | Respeita rate limit |

---

## Processamento com relatório e progresso

Em lotes grandes, acompanhar o progresso é essencial.

```java
import java.util.concurrent.atomic.AtomicInteger;

public class CpfBatchProcessorComProgresso {

 private final CpfAsyncBatchProcessor processor;

 public CpfBatchProcessorComProgresso(
 String apiKey, int concorrencia) {
 this.processor = new CpfAsyncBatchProcessor(
 apiKey, concorrencia
 );
 }

 public RelatorioLote processar(List<String> cpfs) {
 long inicio = System.currentTimeMillis();
 AtomicInteger processados = new AtomicInteger(0);
 int total = cpfs.size();

 List<ResultadoCpf> resultados = cpfs.stream()
 .map(cpf -> cpf.replaceAll("\\D", ""))
 .filter(cpf -> cpf.length() == 11)
 .map(cpf -> {
 ResultadoCpf resultado =
 processor.apiClient.consultar(cpf);
 int atual = processados.incrementAndGet();
 if (atual % 100 == 0 || atual == total) {
 double progresso =
 (atual * 100.0) / total;
 System.out.printf(
 "Progresso: %d/%d (%.1f%%)%n",
 atual, total, progresso
 );
 }
 return resultado;
 })
 .toList();

 long duracao = System.currentTimeMillis() - inicio;

 long sucessos = resultados.stream()
 .filter(ResultadoCpf::sucesso).count();
 long falhas = resultados.size() - sucessos;

 return new RelatorioLote(
 total, sucessos, falhas, duracao, resultados
 );
 }
}

public record RelatorioLote(
 int total,
 long sucessos,
 long falhas,
 long duracaoMs,
 List<ResultadoCpf> resultados
) {
 public double taxaSucesso() {
 return total > 0
 ? (sucessos * 100.0) / total : 0;
 }

 public void imprimir() {
 System.out.println("--- Relatorio de Lote ---");
 System.out.printf("Total: %d%n", total);
 System.out.printf("Sucessos: %d%n", sucessos);
 System.out.printf("Falhas: %d%n", falhas);
 System.out.printf("Taxa: %.1f%%%n", taxaSucesso());
 System.out.printf("Duracao: %dms%n", duracaoMs);
 }
}
```

---

## Perguntas frequentes

### Quantas threads devo usar para processar CPFs em lote com CompletableFuture?
Para a maioria dos casos, entre 10 e 20 threads oferecem o melhor equilíbrio entre velocidade e controle de consumo. Com latência média de ~900ms por consulta, 10 threads paralelas processam cerca de 11 CPFs por segundo. Aumente o pool gradualmente e monitore o tempo de resposta; se ele começar a subir, reduza a concorrência.

### A API CPFHub.io bloqueia requisições quando o limite mensal é atingido?
Não. A CPFHub.io nunca retorna 429 nem bloqueia o serviço. Quando o limite do plano é ultrapassado, as consultas adicionais continuam funcionando normalmente e são cobradas a R$ 0,15 cada. Para evitar cobranças inesperadas em lotes grandes, implemente um contador local que interrompe o processamento ao atingir o limite desejado.

### Como estruturar o processamento de lotes muito grandes (acima de 10.000 CPFs)?
Divida o lote em sub-lotes de 100 a 500 CPFs, introduza um delay de 1 a 5 segundos entre cada sub-lote e persista os resultados intermediários para permitir retomada em caso de falha. Use o `RelatorioLote` para acompanhar o progresso e identifique os CPFs com erro para reprocessamento seletivo.

### Como garantir conformidade com a LGPD ao processar CPFs em lote?
Processe apenas os CPFs para os quais há base legal documentada (consentimento, contrato ou obrigação regulatória), conforme orienta a [ANPD](https://www.gov.br/anpd). Não armazene os CPFs em texto puro após o processamento, registre logs de auditoria com hash do CPF em vez do número real e aplique política de retenção mínima para os dados processados.

### 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

A combinação de Java Streams com CompletableFuture oferece uma solução poderosa e flexível para processamento em lote de CPFs. Streams provêm uma API declarativa para transformação e agregação de dados, enquanto CompletableFuture com executor customizado garante concorrência controlada e eficiente. O relatório de progresso e a agregação de resultados completam a solução, tornando-a adequada para operações de produção.

Cadastre-se em [cpfhub.io](https://www.cpfhub.io/) — 50 consultas mensais gratuitas, sem cartão de crédito — e comece a processar lotes de CPF com alta performance diretamente do seu ambiente Java.

