<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Fabio Rocha</title>
    <description>The latest articles on DEV Community by Fabio Rocha (@fabiothomazrocha).</description>
    <link>https://dev.to/fabiothomazrocha</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F594014%2Fcb7399e6-1794-429d-b24b-6f61119ceb02.jpg</url>
      <title>DEV Community: Fabio Rocha</title>
      <link>https://dev.to/fabiothomazrocha</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/fabiothomazrocha"/>
    <language>en</language>
    <item>
      <title>Virtual Threads – adeus, `OutOfMemoryError`; olá, milhões de threads</title>
      <dc:creator>Fabio Rocha</dc:creator>
      <pubDate>Wed, 10 Jun 2026 16:38:02 +0000</pubDate>
      <link>https://dev.to/fabiothomazrocha/virtual-threads-adeus-outofmemoryerror-ola-milhoes-de-threads-188p</link>
      <guid>https://dev.to/fabiothomazrocha/virtual-threads-adeus-outofmemoryerror-ola-milhoes-de-threads-188p</guid>
      <description>&lt;p&gt;E aí, dev que vive de &lt;code&gt;ExecutorService&lt;/code&gt;e &lt;code&gt;CompletableFuture&lt;/code&gt;? Já tentou criar 10 mil threads na mão? O &lt;code&gt;OutOfMemoryError&lt;/code&gt; vem cantando. Isso porque as threads clássicas do Java (agora chamadas de &lt;strong&gt;platform threads&lt;/strong&gt;) são, basicamente, wrappers finos de threads do sistema operacional. Elas são &lt;strong&gt;caras&lt;/strong&gt; – cada uma consome cerca de 20 MB de memória só de pilha (stack).&lt;/p&gt;

&lt;p&gt;Mas a partir do &lt;strong&gt;JDK 21&lt;/strong&gt;, temos as &lt;strong&gt;virtual threads&lt;/strong&gt; – leves, baratas. Vamos entender sem mistério.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚠️ Qual é o problema das Platform Threads (as de sempre)?
&lt;/h2&gt;

&lt;p&gt;Uma thread de plataforma = uma thread do SO.&lt;br&gt;&lt;br&gt;
Isso é caro em &lt;strong&gt;tempo&lt;/strong&gt; (criação lenta) e &lt;strong&gt;espaço&lt;/strong&gt; (stack enorme: ~20 MB).&lt;br&gt;&lt;br&gt;
O escalonador do SO também tem trabalho para trocar o contexto entre elas.&lt;/p&gt;

&lt;p&gt;Faça as contas: numa máquina com 8 GB de RAM, cabem &lt;strong&gt;no máximo umas 400 threads&lt;/strong&gt; (8.000 MB / 20 MB = 400). Se essas threads ficarem a maior parte do tempo esperando I/O (como acesso a banco, chamadas HTTP), o &lt;strong&gt;CPU fica ocioso&lt;/strong&gt; – você mal usa o hardware.&lt;/p&gt;

&lt;p&gt;Exemplo: uma thread trabalha &lt;strong&gt;0,001 ms&lt;/strong&gt; preparando um request, depois espera &lt;strong&gt;100 ms&lt;/strong&gt; pela resposta da rede. Nesse cenário, ela fica parada &lt;strong&gt;99,99%&lt;/strong&gt; do tempo. Com 400 threads, o uso de CPU é menor que 1%. Um desperdício.&lt;/p&gt;

&lt;p&gt;A solução tradicional? Usar pools de threads, &lt;code&gt;CompletableFuture&lt;/code&gt;, programação reativa (Mono/Flux). Mas o código fica mais complexo, cheio de callbacks.&lt;/p&gt;


&lt;h2&gt;
  
  
  ✨ Virtual Threads – a revolução
&lt;/h2&gt;

&lt;p&gt;Virtual threads são &lt;strong&gt;leves&lt;/strong&gt; – cada uma ocupa &lt;strong&gt;poucos bytes&lt;/strong&gt; (não megabytes) e vivem no &lt;strong&gt;heap da JVM&lt;/strong&gt;, não na pilha do SO. Criar uma virtual thread é &lt;strong&gt;~1000 vezes mais barato&lt;/strong&gt; que uma platform thread. Elas são gerenciadas pelo próprio JVM, não pelo escalonador do SO.&lt;/p&gt;

&lt;p&gt;A arquitetura é de &lt;strong&gt;muitos para poucos&lt;/strong&gt;: milhões de virtual threads rodam em cima de um pequeno número de platform threads (as velhas conhecidas). Cada platform thread executa uma virtual thread por vez, e quando a virtual thread bloqueia (ex: aguardando I/O), a JVM desmonta ela dali e a platform thread pega outra virtual thread pronta. Isso chama-se &lt;strong&gt;escalonamento cooperativo&lt;/strong&gt; – ninguém fica parado.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;📌 &lt;strong&gt;Nota importante&lt;/strong&gt;:&lt;br&gt;&lt;br&gt;
Virtual threads &lt;strong&gt;não devem ser pooladas&lt;/strong&gt; nem reutilizadas. São descartáveis, baratas, criamos uma para cada tarefa concorrente e pronto.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  🛠️ Criando e usando virtual threads na prática
&lt;/h2&gt;

&lt;p&gt;Vamos pegar uma tarefa simples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Runnable&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;currentThread&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  1. Criar e já iniciar com &lt;code&gt;startVirtualThread()&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Thread&lt;/span&gt; &lt;span class="n"&gt;vThread&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;startVirtualThread&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;vThread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"minhaVirtual"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Usando &lt;code&gt;Thread.ofVirtual()&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Thread&lt;/span&gt; &lt;span class="n"&gt;vThread&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofVirtual&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Com nome&lt;/span&gt;
&lt;span class="nc"&gt;Thread&lt;/span&gt; &lt;span class="n"&gt;vThread&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofVirtual&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"minhaVirtual"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Usando &lt;code&gt;Thread.Builder&lt;/code&gt; (útil para várias)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofVirtual&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"vt-"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;Thread&lt;/span&gt; &lt;span class="n"&gt;t1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;t1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;join&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="nc"&gt;Thread&lt;/span&gt; &lt;span class="n"&gt;t2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;t2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;join&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Criar sem iniciar (&lt;code&gt;unstarted&lt;/code&gt;)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Thread&lt;/span&gt; &lt;span class="n"&gt;vThread&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofVirtual&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;unstarted&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// depois...&lt;/span&gt;
&lt;span class="n"&gt;vThread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. &lt;code&gt;ThreadFactory&lt;/code&gt; de virtual threads
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;ThreadFactory&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofVirtual&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"worker-"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;factory&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="nc"&gt;Thread&lt;/span&gt; &lt;span class="n"&gt;vThread&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newThread&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;vThread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fácil, né? Você usa a mesma API &lt;code&gt;java.lang.Thread&lt;/code&gt; que já conhece. Não precisa aprender &lt;code&gt;CompletableFuture&lt;/code&gt; ou reactive streams – o código fica &lt;strong&gt;sequencial e legível&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔍 Detalhes que você precisa saber
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;isVirtual()&lt;/code&gt; → retorna &lt;code&gt;true&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;É &lt;strong&gt;daemon&lt;/strong&gt; por padrão (&lt;code&gt;isDaemon()&lt;/code&gt; = &lt;code&gt;true&lt;/code&gt;). Não tente mudar.&lt;/li&gt;
&lt;li&gt;Prioridade sempre &lt;code&gt;NORM_PRIORITY&lt;/code&gt; (5). &lt;code&gt;setPriority()&lt;/code&gt; não tem efeito.&lt;/li&gt;
&lt;li&gt;Não pertence a &lt;code&gt;ThreadGroup&lt;/code&gt; tradicional – o grupo se chama "VirtualThreads".&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;toString()&lt;/code&gt; mostra algo como: &lt;code&gt;VirtualThread[#22]/runnable@ForkJoinPool-1-worker-1&lt;/code&gt;. O &lt;code&gt;ForkJoinPool-1-worker-1&lt;/code&gt; é a &lt;strong&gt;carrier thread&lt;/strong&gt; (platform thread) que está executando sua virtual thread.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🚀 Quantas virtual threads conseguimos criar?
&lt;/h2&gt;

&lt;p&gt;Rode isso (com cuidado, porque ele trava o sistema só depois de milhões):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;AtomicLong&lt;/span&gt; &lt;span class="n"&gt;contador&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AtomicLong&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;startVirtualThread&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;contador&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;incrementAndGet&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Virtual thread: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;LockSupport&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;park&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// trava a thread virtual&lt;/span&gt;
    &lt;span class="o"&gt;});&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No meu notebook, depois de &lt;strong&gt;14 milhões&lt;/strong&gt; de virtual threads o sistema ficou lento (GC atuando forte), mas &lt;strong&gt;não estourou memória&lt;/strong&gt;. Isso é outro patamar de concorrência.&lt;/p&gt;




&lt;h2&gt;
  
  
  ❗ Mitos que você deve esquecer
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"Virtual threads são mais rápidas"&lt;/strong&gt; – &lt;strong&gt;ERRADO&lt;/strong&gt;. Elas não tornam código computacional mais rápido. Para CPU pesado, use parallel streams. Virtual threads melhoram &lt;strong&gt;throughput&lt;/strong&gt; (você pode ter milhões esperando I/O), mas não latência.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Devemos poolá-las"&lt;/strong&gt; – &lt;strong&gt;ERRADO&lt;/strong&gt;. Nunca crie um pool de virtual threads. Elas são descartáveis, baratas. Cada tarefa ganha a sua.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Virtual threads são gratuitas"&lt;/strong&gt; – &lt;strong&gt;ERRADO&lt;/strong&gt;. Não são de graça, mas são &lt;strong&gt;1000x mais baratas&lt;/strong&gt; que platform threads.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Se bloquear a virtual thread, a carrier thread também trava"&lt;/strong&gt; – &lt;strong&gt;ERRADO&lt;/strong&gt;. A JVM desmonta a virtual thread bloqueada e a carrier thread pega outra. O bloqueio não afeta outras virtual threads.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔄 Compatibilidade retrógrada
&lt;/h2&gt;

&lt;p&gt;Virtual threads funcionam com:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Blocos &lt;code&gt;synchronized&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ThreadLocal&lt;/code&gt; (mas cuidado com o número excessivo – pode não escalar)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Thread.currentThread()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;interrupt()&lt;/code&gt; e &lt;code&gt;InterruptedException&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ou seja, seu código antigo &lt;strong&gt;simplesmente roda&lt;/strong&gt; em virtual threads – sem modificação. Basta mudar de &lt;code&gt;new Thread(...).start()&lt;/code&gt; para &lt;code&gt;Thread.startVirtualThread(...)&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  📌 Quando usar (e quando não usar)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ✅ Ótimo para:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Muitas tarefas de I/O (chamadas HTTP, leitura de banco, acesso a arquivos)&lt;/li&gt;
&lt;li&gt;Servidores web com alta concorrência&lt;/li&gt;
&lt;li&gt;Substituir &lt;code&gt;CompletableFuture&lt;/code&gt; e código reativo cheio de callbacks&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ❌ Não tão útil para:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Processamento intensivo de CPU (cálculos, criptografia, compressão) – prefira parallel streams ou manter pool de platform threads com tamanho = número de cores&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧠 Exemplo prático: simulando 10.000 chamadas HTTP
&lt;/h2&gt;

&lt;p&gt;Com virtual threads fica limpo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;httpClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HttpClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newHttpClient&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;IntStream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;range&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10_000&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;mapToObj&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;HttpRequest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newBuilder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://api.exemplo.com/users/"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;)).&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toList&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Executors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newVirtualThreadPerTaskExecutor&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;futures&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;submit&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;httpClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;send&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;BodyHandlers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofString&lt;/span&gt;&lt;span class="o"&gt;())))&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toList&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;future&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;futures&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;future&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Criamos 10.000 virtual threads &lt;strong&gt;ao mesmo tempo&lt;/strong&gt;, cada uma esperando I/O. Com platform threads, isso seria impossível sem um pool enorme e &lt;code&gt;OutOfMemoryError&lt;/code&gt;. Com virtual threads, roda liso.&lt;/p&gt;




&lt;h2&gt;
  
  
  🎯 Conclusão
&lt;/h2&gt;

&lt;p&gt;Virtual threads são um divisor de águas para concorrência em Java:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✔️ Crie &lt;strong&gt;milhões&lt;/strong&gt; delas sem medo&lt;/li&gt;
&lt;li&gt;✔️ Código &lt;strong&gt;simples e bloqueante&lt;/strong&gt; (nada de callbacks)&lt;/li&gt;
&lt;li&gt;✔️ Aproveite ao máximo o hardware em tarefas de I/O&lt;/li&gt;
&lt;li&gt;✔️ Compatível com todo o ecossistema Java&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Então da próxima vez que pensar em &lt;code&gt;CompletableFuture&lt;/code&gt; ou reactive streams, pergunte-se: &lt;strong&gt;dá pra resolver com virtual threads?&lt;/strong&gt; Muitas vezes, a resposta é sim – com muito menos neuras.&lt;/p&gt;




&lt;p&gt;Gostou? Testa aí no seu código com JDK 21+ e me conta o que achou. E lembre-se: thread virtual não é mais rápida, mas você pode ter tantas que parece mágica. ✨&lt;/p&gt;

&lt;h1&gt;
  
  
  Java21 #VirtualThreads #Concorrência #ProgramaçãoAssíncrona #JDK
&lt;/h1&gt;




&lt;p&gt;&lt;em&gt;Quer mais? O próximo post vai mostrar como usar virtual threads com Spring Boot e comparar performance com reactive. Até lá!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>backend</category>
      <category>java</category>
      <category>performance</category>
      <category>programming</category>
    </item>
    <item>
      <title>Kafka sem duplicação – 2 padrões pra você dormir em paz</title>
      <dc:creator>Fabio Rocha</dc:creator>
      <pubDate>Wed, 27 May 2026 19:57:38 +0000</pubDate>
      <link>https://dev.to/fabiothomazrocha/kafka-sem-duplicacao-2-padroes-pra-voce-dormir-em-paz-h2o</link>
      <guid>https://dev.to/fabiothomazrocha/kafka-sem-duplicacao-2-padroes-pra-voce-dormir-em-paz-h2o</guid>
      <description>&lt;h2&gt;
  
  
  🔁 Kafka sem duplicação – 3 padrões? Não, 2 bastam! (os que realmente funcionam)
&lt;/h2&gt;

&lt;p&gt;E aí, pessoa desenvolvedora de microsserviços? Já teve pesadelo com mensagem duplicada no Kafka? Já processou o mesmo evento duas vezes e olhou pro banco de dados com aquele suor frio descendo a espinha? Pois é. Duplicação é a pedra no sapato de qualquer sistema distribuído.&lt;/p&gt;

&lt;p&gt;Mas calma. Hoje a gente vai focar nos &lt;strong&gt;dois padrões que realmente resolvem o problema sem firula&lt;/strong&gt;: Consumidor Idempotente e Transactional Outbox. Nada de Kafka Transaction API (que, entre nós, mais atrapalha do que ajuda quando tem banco relacional no meio). Pega um café, bora codar com menos susto e mais confiança! 🚀&lt;/p&gt;




&lt;h2&gt;
  
  
  O cenário clássico que dá pesadelo
&lt;/h2&gt;

&lt;p&gt;Imagine que seu serviço consome uma mensagem de um tópico, faz o seguinte pipeline:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;🔁 Faz um POST em uma API externa&lt;/li&gt;
&lt;li&gt;💾 Insere um registro no banco de dados&lt;/li&gt;
&lt;li&gt;📤 Publica um evento em um tópico de saída&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Seu código consome a mensagem, processa e só depois de tudo isso &lt;strong&gt;commit&lt;/strong&gt; do offset no Kafka.&lt;/p&gt;

&lt;p&gt;Aí o sistema morre bem no meio. O que acontece? A mensagem ainda não foi marcada como consumida. Kafka acha que o consumidor morreu e rebalanceia a partição para outra instância, que vai pegar &lt;strong&gt;exatamente a mesma mensagem&lt;/strong&gt; e processar de novo. Resultado: POST duplicado, INSERT duplicado, evento duplicado. E você na UTI do sistema às 3 da manhã. ☠️&lt;/p&gt;

&lt;p&gt;Mas calma, que temos &lt;strong&gt;dois padrões matadores&lt;/strong&gt; para resolver isso. Bora?&lt;/p&gt;




&lt;h2&gt;
  
  
  🔑 Padrão 1: Consumidor Idempotente
&lt;/h2&gt;

&lt;p&gt;A ideia aqui é simples: seu consumidor pode receber a mesma mensagem várias vezes, mas &lt;strong&gt;só processa uma vez&lt;/strong&gt; por meio de IDs únicos armazenados.&lt;/p&gt;

&lt;h3&gt;
  
  
  Como funciona na prática:
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. ID único (Idempotency Key):&lt;/strong&gt; Sua mensagem precisa carregar um identificador único, tipo &lt;code&gt;transactionId&lt;/code&gt; ou &lt;code&gt;eventId&lt;/code&gt;. Esse ID pode vir no header do Kafka ou dentro do payload.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Tabela de mensagens processadas:&lt;/strong&gt; No mesmo banco onde você faz as escritas do seu negócio, cria uma tabelinha "processed_messages" com a chave primária sendo esse ID.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Processamento atômico:&lt;/strong&gt; Você insere o ID nessa tabela na &lt;strong&gt;mesma transação&lt;/strong&gt; que as outras escritas do banco. Se der pau no meio, tudo rola junto!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Transactional&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;processarEvento&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ConsumerRecord&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;OrderEvent&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;eventId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;lastHeader&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"eventId"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Se já existe, é duplicata&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;processedMessageRepo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;existsById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;eventId&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"📌 Evento {} já processado. Pulando!"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;eventId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Processa o negócio&lt;/span&gt;
    &lt;span class="n"&gt;orderService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;criarPedido&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

    &lt;span class="c1"&gt;// Marca como processado na MESMA transação&lt;/span&gt;
    &lt;span class="n"&gt;processedMessageRepo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ProcessedMessage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;eventId&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Se &lt;code&gt;criarPedido&lt;/code&gt; falhar, a inserção na tabela &lt;code&gt;processed_messages&lt;/code&gt; também rola de volta automaticamente. O &lt;code&gt;@Transactional&lt;/code&gt; garante a atomicidade.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vantagem:&lt;/strong&gt; Simples, funciona com qualquer banco relacional.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Desvantagem:&lt;/strong&gt; Não resolve sozinho a publicação atômica de eventos no Kafka (pra isso entra o padrão 2).&lt;/p&gt;


&lt;h2&gt;
  
  
  📦 Padrão 2: Transactional Outbox
&lt;/h2&gt;

&lt;p&gt;Esse padrão resolve um outro problema clássico: você precisa publicar um evento no Kafka e gravar no banco de dados, mas as duas ações &lt;strong&gt;não podem ser atômicas&lt;/strong&gt; usando o mesmo contexto transacional.&lt;/p&gt;

&lt;p&gt;A solução é:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Tabela Outbox:&lt;/strong&gt; Em vez de publicar direto no Kafka, você insere o evento em uma tabela &lt;code&gt;outbox&lt;/code&gt; na &lt;strong&gt;mesma transação&lt;/strong&gt; que suas escritas de negócio.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CDC (Change Data Capture):&lt;/strong&gt; Ferramentas como Debezium monitoram a tabela &lt;code&gt;outbox&lt;/code&gt; e publicam os eventos no tópico final de forma garantida e idempotente.
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;BEGIN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;-- 1. Escrita de negócio&lt;/span&gt;
  &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;pedidos&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;valor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cliente_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;-- 2. Evento vai pra tabela outbox (na mesma transação!)&lt;/span&gt;
  &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;outbox&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aggregate_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'PedidoCriado'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'{"valor": 100.00}'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;COMMIT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Com CDC, o evento vai parar no tópico final sem risco de inconsistência entre banco de dados e Kafka.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vantagem:&lt;/strong&gt; Publicação confiável e atômica com o banco.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Desvantagem:&lt;/strong&gt; Requer infra adicional (Debezium, Kafka Connect), e o CDC pode ter latência de segundos.&lt;/p&gt;


&lt;h2&gt;
  
  
  👑 O combo campeão: consumidor idempotente + outbox
&lt;/h2&gt;

&lt;p&gt;Agora junta os dois: o &lt;strong&gt;padrão 1 garante que a mesma mensagem não vai ser processada duas vezes no consumidor&lt;/strong&gt;, e o &lt;strong&gt;padrão 2 garante que os eventos de saída serão publicados de forma confiável&lt;/strong&gt; sem duplicação no lado do produtor.&lt;/p&gt;

&lt;p&gt;Juntos, eles formam a base de qualquer microsserviço que se preze:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Transactional&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;processarEventoIntegrado&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ConsumerRecord&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Evento&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 1. Idempotent Consumer: verifica se já processou&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;eventId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;lastHeader&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"eventId"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;processedMessageRepo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;existsById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;eventId&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// 2. Chama API externa (não tem jeito, aqui pode dar duplicata)&lt;/span&gt;
    &lt;span class="c1"&gt;//    Mas aí é problema da API externa ser idempotente, não nosso :)&lt;/span&gt;
    &lt;span class="n"&gt;apiExterna&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;criarRecurso&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getDados&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

    &lt;span class="c1"&gt;// 3. Escrita de negócio + outbox (tudo na mesma transação)&lt;/span&gt;
    &lt;span class="n"&gt;pedidoService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;criarPedido&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getPedido&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="n"&gt;outboxRepo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;criarEventoOutbox&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;()));&lt;/span&gt;

    &lt;span class="c1"&gt;// 4. Marca como processado (ainda na mesma transação)&lt;/span&gt;
    &lt;span class="n"&gt;processedMessageRepo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ProcessedMessage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;eventId&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pronto. Você dorme em paz.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚠️ Melhores práticas e dicas quentes que ninguém conta
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1.🔑 Idempotency Keys bem feitas
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;UUID gerado no cliente:&lt;/strong&gt; Mais seguro. O produtor gera, e o ID viaja até o consumidor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chave composta de negócio:&lt;/strong&gt; Exemplo: &lt;code&gt;customerId:orderId:timestamp&lt;/code&gt;. Funciona se a combinação for verdadeiramente única.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coordenadas do Kafka:&lt;/strong&gt; Usar &lt;code&gt;topic-partition-offset&lt;/code&gt; como ID. Simples, mas quebra se você reproduzir o evento a partir de um tópico diferente.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2.⏱️ Cuidado com timeouts e rebalanceamento
&lt;/h3&gt;

&lt;p&gt;Configuração &lt;code&gt;max.poll.interval.ms&lt;/code&gt; e &lt;code&gt;max.poll.records&lt;/code&gt; precisa estar alinhada com o tempo médio de processamento do seu lote. Se o consumidor demorar demais e não conseguir chamar &lt;code&gt;poll()&lt;/code&gt; dentro do tempo, o broker acha que ele morreu e redistribui as partições – causando reprocessamento.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.🧹 Limpeza da tabela &lt;code&gt;processed_messages&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Essa tabela pode crescer infinitamente. Crie uma rotina de limpeza assíncrona (ex: job diário que apaga registros com mais de 7 dias).&lt;/p&gt;

&lt;h3&gt;
  
  
  4.🧪 Outbox com polling simples (sem CDC)
&lt;/h3&gt;

&lt;p&gt;Se você não quiser usar Debezium, pode implementar um &lt;strong&gt;polling publisher&lt;/strong&gt; – um scheduler que lê da tabela outbox e publica no Kafka em lotes. Mas aí você assume responsabilidade de idempotência e transação.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.🚫 O que não fazer
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Não&lt;/strong&gt; tente coordenar transações entre Kafka e banco com XA ou JTA. É lento, frágil e geralmente um pesadelo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Não&lt;/strong&gt; use Kafka Transaction API junto com transações de banco. Já vimos que a combinação não é atômica e pode causar perda de dados.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧭 Fluxo de decisão: qual padrão escolher?
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Você só precisa gravar no banco e ponto?
  ├─ Banco relacional → Idempotent Consumer com tabela processed_messages
  └─ NoSQL (Redis, MongoDB) → Use lock distribuído (SETNX) ou tabela auxiliar

Você precisa gravar no banco E publicar eventos no Kafka:
  ├─ Tem budget e infra para CDC (Debezium)? → Transactional Outbox + Idempotent Consumer (recomendado)
  ├─ Quer algo mais leve sem CDC? → Outbox com polling + Idempotent Consumer
  └─ Precisa de latência baixíssima (milissegundos)? → Idempotent Consumer + transação manual com idempotent producer (cuidado!)

Cenário ideal para microsserviços críticos:
  ✅ Idempotent Consumer + Transactional Outbox (com CDC)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  💬 Dúvida comum: por que não usar Kafka Transaction API?
&lt;/h2&gt;

&lt;p&gt;Essa pergunta aparece muito. A resposta direta: &lt;strong&gt;Kafka Transaction API foi feita para cenários de stream processing onde a única fonte de verdade é o próprio Kafka&lt;/strong&gt; (ex: Kafka Streams). Quando você tem um banco relacional no meio, tentar unir as duas transações não traz atomicidade real e ainda introduz complexidade sem necessidade.&lt;/p&gt;

&lt;p&gt;Os padrões 1 e 2 resolvem o problema &lt;strong&gt;de forma comprovada&lt;/strong&gt;, com ferramentas maduras (Debezium, PostgreSQL, etc.) e sem surpresas. Por que inventar moda? 😎&lt;/p&gt;




&lt;h2&gt;
  
  
  🎯 Conclusão
&lt;/h2&gt;

&lt;p&gt;Duplicação de mensagem não é questão de "se" vai acontecer, mas de "quando". As garantias de "at-most-once" e "at-least-once" do Kafka são otimistas demais para sistemas reais. O que salva é o &lt;strong&gt;design idempotente do seu consumidor&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Lembre-se:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A base de tudo é o &lt;strong&gt;Idempotent Consumer&lt;/strong&gt; (eventId + tabela de controle)&lt;/li&gt;
&lt;li&gt;O &lt;strong&gt;Transactional Outbox&lt;/strong&gt; garante a publicação confiável de eventos sem duas fases de commit&lt;/li&gt;
&lt;li&gt;O combo &lt;strong&gt;Idempotent Consumer + Outbox&lt;/strong&gt; é o mais recomendado, robusto e dorme-se em paz&lt;/li&gt;
&lt;li&gt;Esqueça Kafka Transaction API se você tem banco de dados – não é pra você&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Agora é sua vez: já passou por algum apagão de duplicação? Me conta nos comentários como resolveu. E se ainda não passou, já sabe onde mirar quando o caos chegar. Bora codar com menos duplicata e mais paz de espírito! 🛡️🔥&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Quer mais conteúdo sobre Kafka, arquitetura de microsserviços e resiliência? Segue o blog e ativa as notificações – o próximo post vai ser sobre "Dead Letter Queues e como não perder eventos". Não perde!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>kafka</category>
      <category>springboot</category>
      <category>spring</category>
    </item>
    <item>
      <title>Trabalhando com mapMulti() – Transforme seus streams sem neuras</title>
      <dc:creator>Fabio Rocha</dc:creator>
      <pubDate>Wed, 20 May 2026 18:17:18 +0000</pubDate>
      <link>https://dev.to/fabiothomazrocha/trabalhando-com-mapmulti-transforme-seus-streams-sem-neuras-1eic</link>
      <guid>https://dev.to/fabiothomazrocha/trabalhando-com-mapmulti-transforme-seus-streams-sem-neuras-1eic</guid>
      <description>&lt;h2&gt;
  
  
  Trabalhando com &lt;code&gt;mapMulti()&lt;/code&gt; – Transforme seus streams sem neuras
&lt;/h2&gt;

&lt;p&gt;E aí, dev? Tudo certo?&lt;br&gt;&lt;br&gt;
Se você está no JDK 16 ou superior, a &lt;code&gt;Stream API&lt;/code&gt; ganhou um novo brinquedo: o método &lt;code&gt;mapMulti()&lt;/code&gt;. Ele parece meio estranho no começo, mas juro que depois que você entende, começa a usar até pra fazer café ☕.&lt;/p&gt;

&lt;p&gt;A ideia é simples: ele permite mapear cada elemento do stream para &lt;strong&gt;zero, um ou vários&lt;/strong&gt; elementos novos, tudo dentro de um único &lt;code&gt;BiConsumer&lt;/code&gt;. Chega de ficar encadeando &lt;code&gt;filter()&lt;/code&gt; e &lt;code&gt;map()&lt;/code&gt; que nem louco? Bom, nem sempre, mas em muitos casos sim. Vamos ver na prática.&lt;/p&gt;


&lt;h2&gt;
  
  
  🔍 Exemplo clássico vs &lt;code&gt;mapMulti()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Imagina que você tem uma lista de notas de alunos e quer filtrar só as notas maiores ou iguais a 7 e aplicar um bônus de 10% em cada uma.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Modo tradicional (filter + map):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Double&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;notas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;5.5&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;8.0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;6.0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;9.5&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;7.0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;4.0&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Double&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;notasComBonus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;notas&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.10&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;collect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Collectors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toList&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

&lt;span class="c1"&gt;// Resultado: [8.8, 10.45, 7.7]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora com &lt;code&gt;mapMulti()&lt;/code&gt; a gente faz a mesma coisa com &lt;strong&gt;uma só operação&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Double&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;notasComBonusMM&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;notas&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Double&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;mapMulti&lt;/span&gt;&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="n"&gt;nota&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;consumer&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nota&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;consumer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;accept&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nota&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.10&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;})&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;collect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Collectors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toList&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O &lt;code&gt;if&lt;/code&gt; faz o papel do &lt;code&gt;filter()&lt;/code&gt;, e o &lt;code&gt;accept()&lt;/code&gt; aplica o &lt;code&gt;map()&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
Perceba o &lt;code&gt;.&amp;lt;Double&amp;gt;&lt;/code&gt; antes do &lt;code&gt;mapMulti&lt;/code&gt; – isso é um &lt;strong&gt;type-witness&lt;/strong&gt; (o compilador chato pediu). Sem ele o código não compila. Um pequeno preço a pagar.&lt;/p&gt;


&lt;h2&gt;
  
  
  🧮 Trabalhando com tipos primitivos – &lt;code&gt;mapMultiToDouble()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Se a sua ideia é somar essas notas com bônus, melhor usar a versão especializada para &lt;code&gt;double&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;somaComBonus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;notas&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;mapMultiToDouble&lt;/span&gt;&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="n"&gt;nota&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;consumer&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nota&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;consumer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;accept&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nota&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.10&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;})&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sum&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;somaComBonus&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 26.95&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sem type-witness, sem firula. E se depois você quiser voltar a ter um &lt;code&gt;Stream&amp;lt;Double&amp;gt;&lt;/code&gt;, é só usar &lt;code&gt;boxed()&lt;/code&gt; ou &lt;code&gt;mapToObj()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Stream&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Double&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;streamBonus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;notas&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;mapMultiToDouble&lt;/span&gt;&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="n"&gt;nota&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;consumer&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nota&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;consumer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;accept&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nota&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.10&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;})&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;boxed&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  📦 &lt;code&gt;flatMap()&lt;/code&gt; vs &lt;code&gt;mapMulti()&lt;/code&gt; – o duelo do one‑to‑many
&lt;/h2&gt;

&lt;p&gt;Vamos para um exemplo mais &lt;em&gt;real&lt;/em&gt;. Temos categorias de produtos, e cada categoria tem vários produtos. Queremos gerar uma lista simples de &lt;code&gt;ProdutoInfo&lt;/code&gt; contendo o nome da categoria + o nome do produto.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Categoria&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;nome&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Produto&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;produtos&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// getters...&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Produto&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;nome&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;preco&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// getters...&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProdutoInfo&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;categoria&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;produto&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// construtor, getters...&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Com &lt;code&gt;flatMap()&lt;/code&gt; (tradicional)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ProdutoInfo&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;listaFlat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;categorias&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flatMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getProdutos&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prod&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ProdutoInfo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getNome&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;prod&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getNome&lt;/span&gt;&lt;span class="o"&gt;()))&lt;/span&gt;
    &lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;collect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Collectors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toList&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O problema aqui é que, &lt;strong&gt;para cada categoria&lt;/strong&gt;, criamos um stream intermediário (o &lt;code&gt;cat.getProdutos().stream()&lt;/code&gt;). Se forem muitas categorias, isso pesa.&lt;/p&gt;

&lt;h3&gt;
  
  
  Com &lt;code&gt;mapMulti()&lt;/code&gt; (mais enxuto)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ProdutoInfo&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;listaMM&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;categorias&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ProdutoInfo&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;mapMulti&lt;/span&gt;&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="n"&gt;categoria&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;consumer&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Produto&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;categoria&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getProdutos&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;consumer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;accept&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ProdutoInfo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;categoria&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getNome&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getNome&lt;/span&gt;&lt;span class="o"&gt;()));&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;})&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;collect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Collectors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toList&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sem streams intermediários, só um laço &lt;code&gt;for&lt;/code&gt; simples. &lt;strong&gt;Mais performático&lt;/strong&gt; e, na minha opinião, mais fácil de ler.&lt;/p&gt;




&lt;h2&gt;
  
  
  🎯 Com filtro adicional – só produtos acima de R$ 50
&lt;/h2&gt;

&lt;p&gt;Se você quiser só produtos com preço maior que 50, a vantagem do &lt;code&gt;mapMulti()&lt;/code&gt; fica ainda mais clara:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ProdutoInfo&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;listaCaros&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;categorias&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ProdutoInfo&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;mapMulti&lt;/span&gt;&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="n"&gt;categoria&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;consumer&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Produto&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;categoria&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getProdutos&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getPreco&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;consumer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;accept&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ProdutoInfo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;categoria&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getNome&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getNome&lt;/span&gt;&lt;span class="o"&gt;()));&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;})&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;collect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Collectors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toList&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Aqui você evita um &lt;code&gt;filter()&lt;/code&gt; extra e mantém tudo no mesmo lugar. E repara: cada categoria tem &lt;strong&gt;poucos produtos&lt;/strong&gt; (geralmente), então se encaixa perfeitamente na recomendação oficial:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Use &lt;code&gt;mapMulti()&lt;/code&gt; quando for substituir cada elemento do stream por um pequeno número (possivelmente zero) de elementos.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🧠 Abordagem imperativa? Também vale!
&lt;/h2&gt;

&lt;p&gt;Sabe aquele método que já faz a lógica toda e recebe um &lt;code&gt;Consumer&lt;/code&gt;? Então... você pode passar ele direto pro &lt;code&gt;mapMulti()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Adicione na classe &lt;code&gt;Categoria&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;produtosCaros&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Consumer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ProdutoInfo&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;consumer&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;limite&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Produto&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;produtos&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getPreco&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;limite&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;consumer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;accept&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ProdutoInfo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;nome&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getNome&lt;/span&gt;&lt;span class="o"&gt;()));&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora seu stream fica &lt;strong&gt;limpo igual água de coco&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ProdutoInfo&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;resultado&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;categorias&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ProdutoInfo&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;mapMulti&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;produtosCaros&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;consumer&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;collect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Collectors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toList&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ou usando method reference (ainda mais lindo):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="o"&gt;.&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ProdutoInfo&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;mapMulti&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;produtosCaros&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;consumer&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
&lt;span class="c1"&gt;// ou, se o limite for fixo:&lt;/span&gt;
&lt;span class="o"&gt;.&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ProdutoInfo&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;mapMulti&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;produtosCaros&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;consumer&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;(Na real com method reference você teria que adaptar, mas a ideia é que o &lt;code&gt;mapMulti&lt;/code&gt; aceita um lambda que chama seu método imperativo.)&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  ✅ Quando usar &lt;code&gt;mapMulti()&lt;/code&gt; – resumo da ópera
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Você tem uma relação &lt;strong&gt;one‑to‑zero/one‑to‑many&lt;/strong&gt; e o número de elementos gerados por elemento original é &lt;strong&gt;pequeno&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Quer &lt;strong&gt;evitar criar streams intermediários&lt;/strong&gt; (melhor performance em laços grandes).&lt;/li&gt;
&lt;li&gt;O código fica &lt;strong&gt;mais claro&lt;/strong&gt; com um laço &lt;code&gt;for&lt;/code&gt; e &lt;code&gt;if&lt;/code&gt; do que com &lt;code&gt;flatMap&lt;/code&gt; + &lt;code&gt;filter&lt;/code&gt; + &lt;code&gt;map&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Você já tem um método que recebe &lt;code&gt;Consumer&lt;/code&gt; e quer integrar direto no stream.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;O &lt;code&gt;mapMulti()&lt;/code&gt; não vai matar o &lt;code&gt;flatMap()&lt;/code&gt; – cada um tem seu lugar. Mas quando o cenário se encaixa, é como trocar uma chave de fenda por uma chave Philips: faz o mesmo serviço, mas com muito mais estilo.&lt;/p&gt;




&lt;p&gt;Gostou? Testa aí no seu código e me conta se os streams ficaram mais limpos. Até a próxima, e bora codar com menos firula e mais resultado! 🚀&lt;/p&gt;

</description>
      <category>java</category>
      <category>spring</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
