Quando usamos .parallelStream() no Java, esperamos que tudo rode em paralelo — rápido, eficiente, multicore.

Mas às vezes, mesmo usando .parallelStream(), tudo roda em uma única thread. Cadê o paralelo? 😵


🧪 O problema apareceu numa task assíncrona

No meu caso, eu estava executando uma task assíncrona dentro de um ThreadPoolTaskExecutor (Spring).

Tudo parecia certo: a lógica usava .parallelStream() e tinha bastante dados pra processar.

List<String> usuarios = List.of("alice", "bob");
List<String> produtos = IntStream.range(0, 10_000)
    .mapToObj(i -> "produto-" + i)
    .toList();

List<String> resultado = usuarios.stream()
    .flatMap(usuario ->
        produtos.parallelStream()
            .map(produto -> {
                System.out.println("Map sequencial: " + usuario + " - " + produto + " na " + Thread.currentThread().getName());
                return usuario + " → " + produto;
            })
    )
    .toList();

Mas ao rodar… algo estranho:

📉 Tudo acontecia em uma única thread!

Quando imprimi os nomes das threads, vi que era sempre task-executor-1.


🧠 Por que esse código não paraleliza nada?

Esse código parece correto, mas tem uma armadilha:

Errata: Uma explicação muito mais simples e melhor foi dada pelo brother aleatorio.dev.br no bsky. "A resposta está na implementação do flatmap que pega um stream paralelo e chama o método sequential para forçar a execução sequencial do stream". Eu recomendo ler a thread dele que eu vou postar nos comentários.

  1. O usuarios.stream() é sequencial
  2. O .flatMap(...) processa um usuário por vez
  3. O .parallelStream() interno só descreve o trabalho, ele é lazy
  4. Quem consome o stream interno é o flatMap externo
  5. Como o flatMap é sequencial, ele consome um stream paralelo por vez
  6. E esse consumo acontece na mesma thread externa

Mesmo com .parallelStream(), a execução é linear e presa na thread da task.


✅ A solução de verdade: inverter os streams

A real solução é trazer o .parallelStream() para o nível mais alto — geralmente onde está a maior lista.

Se a lista de produtos é muito maior que a de usuarios, o ideal é inverter a ordem:

List<String> resultado = produtos.parallelStream()
    .flatMap(produto ->
        usuarios.stream()
            .map(usuario -> {
                System.out.println("Final paralelo: " + usuario + " - " + produto + " na " + Thread.currentThread().getName());
                return usuario + " → " + produto;
            })
    )
    .toList();

Agora sim 🎉

Você verá saídas como:

Final paralelo: bob - produto-991 na ForkJoinPool.commonPool-worker-7  
Final paralelo: alice - produto-458 na ForkJoinPool.commonPool-worker-1

✅ O paralelismo foi ativado de verdade

✅ CPU sendo usada

✅ Threads trabalhando

✅ Job mais rápido!


🕒 Resultado real

No meu caso, essa simples mudança — inverter os streams — reduziu o tempo de execução de cerca de 45 minutos para apenas 10 minutos.

Uma melhoria absurda com pouquíssimas linhas de código!


✅ TL;DR

  • ✅ Use .parallelStream() na lista maior
  • 🚫 Não coloque .parallelStream() dentro de .flatMap() que vem de .stream() sequencial
  • 🧠 O problema real é o flatMap consumindo os substreams um por vez
  • 🚀 Inverta a ordem dos streams e traga o paralelismo para fora
  • ⏱️ Pode reduzir drasticamente o tempo de execução — no meu caso, de 45min → 10min

Se você curtiu, deixa um ❤️, comenta, ou compartilha!

Tem mais truques de performance em Java que você usa? Bora trocar ideia 👇