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.