Sistemas distribuídos, 05/11/2026, 8 min de leitura

Cuidados ao usar CompletableFuture para chamadas remotas

Sem executor próprio e sem timeout, o CompletableFuture paralelo vira um gargalo compartilhado por todo o sistema.

O CompletableFuture deixa disparar várias chamadas em paralelo e combinar os resultados. Funciona muito bem, até o dia em que uma dependência lenta trava a aplicação inteira.

1. O pool padrão é compartilhado

Sem passar um executor, supplyAsync usa o ForkJoinPool.commonPool(). Ele é dimensionado pelo número de CPUs e serve a toda a JVM. Chamada de rede é bloqueante: poucas requisições lentas ocupam todas as threads e o resto da aplicação, inclusive outras partes que usam o mesmo pool, para de progredir.

// evite: bloqueia o pool comum
CompletableFuture.supplyAsync(() -> clienteHttp.buscar(id));

// prefira: pool dedicado e dimensionado para I/O
CompletableFuture.supplyAsync(() -> clienteHttp.buscar(id), ioExecutor);

2. Não existe timeout por padrão

Um get() ou join() sem prazo espera para sempre. Desde o Java 9 há suporte direto:

CompletableFuture.supplyAsync(() -> clienteHttp.buscar(id), ioExecutor)
    .orTimeout(2, TimeUnit.SECONDS)
    .exceptionally(ex -> respostaAlternativa());

Use também timeout no próprio cliente HTTP. O do future não interrompe a chamada que já está em curso.

3. Cancelar não cancela o trabalho

cancel() completa o future, mas não interrompe a thread que está executando. A chamada segue consumindo recurso até terminar.

4. Exceções vêm embrulhadas

join() lança CompletionException e get() lança ExecutionException. A causa real está em getCause(). Trate isso, ou toda falha vai parecer a mesma.

5. allOf não devolve os resultados

CompletableFuture.allOf(...) retorna Void. Guarde as referências dos futures e leia cada um depois de a espera terminar. Se um falhar, o allOf falha, mas os demais continuam rodando.

Regra prática

Paralelizar aumenta a carga sobre a dependência, e nem toda dependência aguenta. Defina antes o número máximo de chamadas simultâneas, o timeout e o comportamento em falha. Se essas três respostas não existem, o paralelismo é um risco e não uma otimização.