<?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.us-east-2.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>🧵 ThreadLocal no Java 21 – o que mudou, o que continua e quando você deve (ou não) usar</title>
      <dc:creator>Fabio Rocha</dc:creator>
      <pubDate>Wed, 29 Jul 2026 23:04:16 +0000</pubDate>
      <link>https://dev.to/fabiothomazrocha/threadlocal-no-java-21-o-que-mudou-o-que-continua-e-quando-voce-deve-ou-nao-usar-36gi</link>
      <guid>https://dev.to/fabiothomazrocha/threadlocal-no-java-21-o-que-mudou-o-que-continua-e-quando-voce-deve-ou-nao-usar-36gi</guid>
      <description>&lt;p&gt;E aí, dev que já passou horas debugando um &lt;code&gt;ThreadLocal&lt;/code&gt; que não foi limpo e vazou memória? Pois é. Esse velho conhecido do Java 1.2 sempre foi a solução para guardar dados "por thread" como o &lt;code&gt;userId&lt;/code&gt; de uma requisição, ou uma conexão de banco que não pode ser compartilhada.&lt;/p&gt;

&lt;p&gt;Mas aí chegaram as &lt;strong&gt;Virtual Threads&lt;/strong&gt; no JDK 21, e o jogo mudou. Criar milhões de threads leves virou realidade, e o bom e velho &lt;code&gt;ThreadLocal&lt;/code&gt; começou a mostrar suas fraquezas. A boa notícia? O Java 21 trouxe uma alternativa moderna e elegante: &lt;strong&gt;Scoped Values&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Vamos entender tudo isso com exemplos práticos. Bora? 🚀&lt;/p&gt;




&lt;h2&gt;
  
  
  🔍 O que é ThreadLocal (e por que você usava isso)?
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ThreadLocal&lt;/code&gt; é uma classe que permite criar variáveis que &lt;strong&gt;só podem ser acessadas e modificadas pela mesma thread&lt;/strong&gt;. Cada thread que acessa um &lt;code&gt;ThreadLocal&lt;/code&gt; tem sua própria cópia independente da variável.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Exemplo clássico:&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="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ContextoUsuario&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ThreadLocal&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;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;USER_ID&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;ThreadLocal&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;setUserId&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;userId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="no"&gt;USER_ID&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;set&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;getUserId&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="no"&gt;USER_ID&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="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;clear&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="no"&gt;USER_ID&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;remove&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// 👈 NUNCA ESQUEÇA DISSO!&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Em algum lugar do código...&lt;/span&gt;
&lt;span class="nc"&gt;ContextoUsuario&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setUserId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"alice123"&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;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ContextoUsuario&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getUserId&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// "alice123"&lt;/span&gt;
&lt;span class="nc"&gt;ContextoUsuario&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;clear&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Use quando:&lt;/strong&gt; precisar associar dados a uma thread específica sem passar parâmetros por toda a cadeia de chamadas tipo &lt;code&gt;userId&lt;/code&gt; numa requisição web, conexão de banco, ou transação.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚠️ Os problemas clássicos do ThreadLocal (que todo mundo já sofreu)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. 💀 Memory Leak
&lt;/h3&gt;

&lt;p&gt;Se você esquecer de chamar &lt;code&gt;remove()&lt;/code&gt;, o valor fica preso na thread. Se a thread for reaproveitada (como num pool), o valor vaza e pode nunca ser coletado.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. 🔄 Contaminação entre requisições
&lt;/h3&gt;

&lt;p&gt;Num pool de threads, uma requisição pode "herdar" o &lt;code&gt;ThreadLocal&lt;/code&gt; da requisição anterior, porque a thread foi reaproveitada. Resultado: usuário A vê dados do usuário B. Um pesadelo de segurança.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. 🧪 Dificuldade de testar
&lt;/h3&gt;

&lt;p&gt;O comportamento do &lt;code&gt;ThreadLocal&lt;/code&gt; depende do contexto de execução da thread, o que torna os testes imprevisíveis e difíceis de controlar.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. 🌍 Global state disfarçado
&lt;/h3&gt;

&lt;p&gt;Embora sejam "locais à thread", múltiplas threads compartilham o mesmo código. O uso excessivo cria dependências escondidas que tornam o código difícil de entender.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧵 O problema com Virtual Threads
&lt;/h2&gt;

&lt;p&gt;Aqui a coisa fica séria.&lt;/p&gt;

&lt;p&gt;Virtual threads são &lt;strong&gt;leves e descartáveis&lt;/strong&gt; você pode criar milhões delas. Elas aparecem e desaparecem em frações de milissegundo.&lt;/p&gt;

&lt;p&gt;O &lt;code&gt;ThreadLocal&lt;/code&gt;, por sua vez, foi feito para threads &lt;strong&gt;longas e reutilizáveis&lt;/strong&gt;. Com virtual threads, cada uma pode ter seu próprio &lt;code&gt;ThreadLocal&lt;/code&gt; e, se você estiver armazenando objetos grandes, o &lt;strong&gt;heap pode explodir&lt;/strong&gt; com milhões de cópias.&lt;/p&gt;

&lt;p&gt;Além disso, se a virtual thread terminar e o &lt;code&gt;ThreadLocal&lt;/code&gt; não for limpo, o valor pode continuar ocupando memória mesmo depois que a thread já morreu.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Importante:&lt;/strong&gt; Nas versões preview do JDK 19 e 20, era possível criar virtual threads &lt;strong&gt;sem&lt;/strong&gt; suporte a ThreadLocal. No JDK 21, &lt;strong&gt;todas as virtual threads suportam ThreadLocal&lt;/strong&gt; para garantir compatibilidade com bibliotecas existentes. Mas isso não significa que você deva usar significa que você &lt;strong&gt;precisa ter mais cuidado ainda&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  ✨ A novidade do Java 21: Scoped Values
&lt;/h2&gt;

&lt;p&gt;O Java 21 trouxe os &lt;strong&gt;Scoped Values&lt;/strong&gt; (JEP 446). A ideia é simples e elegante:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Em vez de guardar dados &lt;strong&gt;dentro da thread&lt;/strong&gt; (como o &lt;code&gt;ThreadLocal&lt;/code&gt;), o &lt;code&gt;ScopedValue&lt;/code&gt; &lt;strong&gt;prende o valor a um bloco de código&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Quando o bloco termina, o valor &lt;strong&gt;some automaticamente&lt;/strong&gt; sem &lt;code&gt;remove()&lt;/code&gt;, sem memory leak.&lt;/li&gt;
&lt;li&gt;É &lt;strong&gt;imutável&lt;/strong&gt;: uma vez bindado, não pode ser alterado.&lt;/li&gt;
&lt;li&gt;Funciona perfeitamente com virtual threads.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Exemplo com ScopedValue:&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="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ContextoModerno&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Declara o ScopedValue (geralmente static final)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ScopedValue&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;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;USER_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ScopedValue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newInstance&lt;/span&gt;&lt;span class="o"&gt;();&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;processarRequisicao&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;userId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// "Amarra" o valor a um bloco de código&lt;/span&gt;
        &lt;span class="nc"&gt;ScopedValue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;USER_ID&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;run&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="c1"&gt;// Dentro deste bloco, USER_ID.get() retorna "userId"&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;"Usuário: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="no"&gt;USER_ID&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="n"&gt;chamarOutroMetodo&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// O valor continua disponível&lt;/span&gt;
        &lt;span class="o"&gt;});&lt;/span&gt;
        &lt;span class="c1"&gt;// Fora do bloco, USER_ID.get() lança exceção&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;void&lt;/span&gt; &lt;span class="nf"&gt;chamarOutroMetodo&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Ainda consegue acessar o valor aqui dentro&lt;/span&gt;
        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;USER_ID&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="s"&gt;"Método aninhado: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;user&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;&lt;strong&gt;O que acontece aqui?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;O valor &lt;code&gt;userId&lt;/code&gt; fica disponível &lt;strong&gt;apenas dentro do bloco &lt;code&gt;run()&lt;/code&gt;&lt;/strong&gt; e em todos os métodos chamados a partir dele.&lt;/li&gt;
&lt;li&gt;Quando o bloco termina, o valor é &lt;strong&gt;automaticamente descartado&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Nada de &lt;code&gt;remove()&lt;/code&gt; o Java cuida de tudo.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  📊 Comparativo rápido
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Característica&lt;/th&gt;
&lt;th&gt;ThreadLocal&lt;/th&gt;
&lt;th&gt;ScopedValue (Java 21)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Quando surgiu&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Java 1.2&lt;/td&gt;
&lt;td&gt;Java 21&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Onde os dados ficam&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Dentro da thread (&lt;code&gt;ThreadLocalMap&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Vinculados ao bloco de código (stack)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Limpeza&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Manual (&lt;code&gt;remove()&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Automática&lt;/strong&gt; ao sair do bloco&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Imutável&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌ Pode ser alterado&lt;/td&gt;
&lt;td&gt;✅ &lt;strong&gt;Imutável&lt;/strong&gt; após bind&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Virtual threads&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Funciona, mas com risco de memory leak&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Projetado para virtual threads&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Performance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Busca em hashtable por thread&lt;/td&gt;
&lt;td&gt;Mais eficiente em cenários de curta duração&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Risco&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Memory leak, contaminação entre threads&lt;/td&gt;
&lt;td&gt;Mínimo&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  🧭 Quando usar cada um?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ✅ Use ThreadLocal se:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Você está mantendo código &lt;strong&gt;legado&lt;/strong&gt; que já usa ThreadLocal&lt;/li&gt;
&lt;li&gt;Precisa de um valor que &lt;strong&gt;realmente deve viver durante toda a vida da thread&lt;/strong&gt; (ex: pool de conexões JDBC)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Não está usando virtual threads&lt;/strong&gt; (ou está usando platform threads tradicionais)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ✅ Use ScopedValue se:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Está no &lt;strong&gt;Java 21 ou superior&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Usa &lt;strong&gt;virtual threads&lt;/strong&gt; (ou planeja usar)&lt;/li&gt;
&lt;li&gt;Precisa passar contexto (ex: &lt;code&gt;userId&lt;/code&gt;, &lt;code&gt;transactionId&lt;/code&gt;, &lt;code&gt;tenantId&lt;/code&gt;) por uma cadeia de chamadas&lt;/li&gt;
&lt;li&gt;Quer código mais &lt;strong&gt;seguro, limpo e sem memory leak&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ❌ Evite ThreadLocal se:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Está usando virtual threads e armazenando objetos grandes&lt;/li&gt;
&lt;li&gt;Não quer se preocupar com &lt;code&gt;remove()&lt;/code&gt; e memory leak&lt;/li&gt;
&lt;li&gt;Quer código mais fácil de testar e entender&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  💡 Dica de ouro
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Se você está no Java 21 e usando virtual threads, ScopedValue é o caminho.&lt;/strong&gt; ThreadLocal ainda funciona, mas você vai pagar o preço em memória e complexidade.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;E lembre-se: se for usar ThreadLocal &lt;strong&gt;em qualquer cenário&lt;/strong&gt;, sempre envolva o uso com &lt;code&gt;try-finally&lt;/code&gt; e chame &lt;code&gt;remove()&lt;/code&gt; no finally. É feio, mas é necessário.&lt;/p&gt;




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

&lt;p&gt;O &lt;code&gt;ThreadLocal&lt;/code&gt; serviu bem por décadas, mas o mundo mudou. As virtual threads vieram para ficar, e com elas veio a necessidade de ferramentas mais modernas e seguras.&lt;/p&gt;

&lt;p&gt;O &lt;code&gt;ScopedValue&lt;/code&gt; não é só "um ThreadLocal melhor" é uma &lt;strong&gt;mudança de paradigma&lt;/strong&gt;: dados atrelados a &lt;strong&gt;blocos de código&lt;/strong&gt;, não a threads. Mais seguro, mais performático, mais fácil de raciocinar.&lt;/p&gt;

&lt;p&gt;Da próxima vez que for guardar um &lt;code&gt;userId&lt;/code&gt; ou um contexto de requisição, pergunte-se: &lt;strong&gt;estou no Java 21?&lt;/strong&gt; Se sim, dê uma chance ao &lt;code&gt;ScopedValue&lt;/code&gt;. Sua memória (e seu eu do futuro debugando) vão agradecer. 🧵✨&lt;/p&gt;

&lt;p&gt;Já usou ScopedValue? Ou ainda sofre com ThreadLocal? Conta aí nos comentários! 👇&lt;/p&gt;

&lt;h1&gt;
  
  
  Java21 #VirtualThreads #ThreadLocal #ScopedValue #Concorrência
&lt;/h1&gt;




&lt;p&gt;&lt;em&gt;Quer mais? No próximo post vou mostrar como migrar um código real de ThreadLocal para ScopedValue num projeto Spring Boot. Até lá! 🚀&lt;/em&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>java</category>
      <category>braziliandevs</category>
      <category>springboot</category>
    </item>
    <item>
      <title>"Sistemas não transacionais? Sim, dá pra ter consistência (sem surtar)" – O guia do Saga Pattern, Outbox e idempotência</title>
      <dc:creator>Fabio Rocha</dc:creator>
      <pubDate>Thu, 23 Jul 2026 01:44:50 +0000</pubDate>
      <link>https://dev.to/fabiothomazrocha/sistemas-nao-transacionais-sim-da-pra-ter-consistencia-sem-surtar-o-guia-do-saga-pattern-407c</link>
      <guid>https://dev.to/fabiothomazrocha/sistemas-nao-transacionais-sim-da-pra-ter-consistencia-sem-surtar-o-guia-do-saga-pattern-407c</guid>
      <description>&lt;h2&gt;
  
  
  🧠 O problema clássico: você não está mais no seu banco relacional
&lt;/h2&gt;

&lt;p&gt;Lembra daquela época boa em que você dava um &lt;code&gt;@Transactional&lt;/code&gt; e tudo era feliz? Commit ou rollback, nunca meio termo. Aí o mundo resolveu ir pra microsserviços, bancos NoSQL, filas, eventos… e essa rigidez ACID foi pro espaço.&lt;/p&gt;

&lt;p&gt;O que fazer quando você precisa &lt;strong&gt;garantir que várias operações distribuídas aconteçam de forma consistente&lt;/strong&gt; sem ter um banco que coordene isso tudo?&lt;/p&gt;

&lt;p&gt;A resposta vem em quatro letras: &lt;strong&gt;BASE&lt;/strong&gt; (Basic Availability, Soft state, Eventual consistency). E na prática, isso se traduz em:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Saga Pattern&lt;/strong&gt; (coreografia ou orquestração)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compensação&lt;/strong&gt; (o tal do rollback manual)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Idempotência&lt;/strong&gt; (pra não repetir bagunça)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transactional Outbox&lt;/strong&gt; (pra não perder mensagens)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tabela de estado/log&lt;/strong&gt; (pra saber onde cada saga está)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Vamos destrinchar cada um com exemplos de código (simulando um sistema de e-commerce bem safado).&lt;/p&gt;




&lt;h2&gt;
  
  
  1️⃣ Saga Pattern – quebre a transação grande em pedaços pequenos
&lt;/h2&gt;

&lt;p&gt;A ideia é linda: em vez de tentar fazer um &lt;code&gt;COMMIT&lt;/code&gt; gigante que abrace vários serviços, você divide a operação em &lt;strong&gt;pequenas transações locais&lt;/strong&gt;, cada uma com sua própria ação e sua ação de compensação (caso algo dê errado).&lt;/p&gt;

&lt;p&gt;Existem dois jeitos de orquestrar uma saga:&lt;/p&gt;

&lt;h3&gt;
  
  
  🎭 Coreografia (cada serviço faz sua parte e emite eventos)
&lt;/h3&gt;

&lt;p&gt;Ninguém centraliza o fluxo. Cada serviço escuta eventos e reage.&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="c1"&gt;// Serviço de Pedidos&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;criarPedido&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PedidoCommand&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;pedidoRepo&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;cmd&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// transação local&lt;/span&gt;
    &lt;span class="n"&gt;eventBus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;publish&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;PedidoCriadoEvent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getId&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getClienteId&lt;/span&gt;&lt;span class="o"&gt;()));&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Serviço de Pagamentos (escuta PedidoCriadoEvent)&lt;/span&gt;
&lt;span class="nd"&gt;@EventListener&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;processarPagamento&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PedidoCriadoEvent&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;)&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="n"&gt;pagamentoService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;autorizar&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getClienteId&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getValor&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="n"&gt;eventBus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;publish&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;PagamentoAutorizadoEvent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getPedidoId&lt;/span&gt;&lt;span class="o"&gt;()));&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;eventBus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;publish&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;PagamentoFalhouEvent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getPedidoId&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="c1"&gt;// Serviço de Estoque (escuta PagamentoAutorizadoEvent)&lt;/span&gt;
&lt;span class="nd"&gt;@EventListener&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;reservarEstoque&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PagamentoAutorizadoEvent&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// reserva produtos...&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Vantagem:&lt;/strong&gt; baixo acoplamento, cada serviço só conhece seus eventos.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Desvantagem:&lt;/strong&gt; fluxo fica implícito, difícil de monitorar.&lt;/p&gt;
&lt;h3&gt;
  
  
  🎼 Orquestração (um serviço central manda em tudo)
&lt;/h3&gt;

&lt;p&gt;Aqui o "maestro" dita cada passo e sabe o que fazer em caso de falha.&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PedidoOrquestrador&lt;/span&gt; &lt;span class="o"&gt;{&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;executarSaga&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PedidoCommand&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;)&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="c1"&gt;// Passo 1: Criar pedido&lt;/span&gt;
            &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;pedidoId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pedidoClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;criar&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

            &lt;span class="c1"&gt;// Passo 2: Processar pagamento&lt;/span&gt;
            &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;pagamentoId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pagamentoClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;autorizar&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getClienteId&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getValor&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

            &lt;span class="c1"&gt;// Passo 3: Reservar estoque&lt;/span&gt;
            &lt;span class="n"&gt;estoqueClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;reservar&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getItens&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

            &lt;span class="c1"&gt;// Passo 4: Finalizar&lt;/span&gt;
            &lt;span class="n"&gt;pedidoClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;confirmar&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pedidoId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PagamentoException&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Compensação do passo 1&lt;/span&gt;
            &lt;span class="n"&gt;pedidoClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;cancelar&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pedidoId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SagaException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Pagamento falhou, pedido cancelado"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;EstoqueException&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Compensação do passo 1 e 2&lt;/span&gt;
            &lt;span class="n"&gt;pedidoClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;cancelar&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pedidoId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;pagamentoClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;estornar&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pagamentoId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SagaException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Estoque insuficiente"&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;&lt;strong&gt;Vantagem:&lt;/strong&gt; fluxo explícito, fácil de logar e monitorar.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Desvantagem:&lt;/strong&gt; orquestrador vira ponto de acoplamento.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;✅ &lt;strong&gt;Recomendação:&lt;/strong&gt; comece com orquestração quando o fluxo for complexo e você precisar de visibilidade. Use coreografia quando cada serviço for realmente independente.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  2️⃣ Mecanismo de Compensação – o seu rollback manual
&lt;/h2&gt;

&lt;p&gt;Em sistemas não transacionais, não existe &lt;code&gt;ROLLBACK&lt;/code&gt;. Se a etapa 2 falhou, você precisa &lt;strong&gt;executar uma ação reversa&lt;/strong&gt; da etapa 1. É o famoso "desfazer".&lt;/p&gt;

&lt;p&gt;Exemplo de compensação em um serviço de reserva de hotéis:&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ReservaService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Transactional&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;criarReserva&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ReservaRequest&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// ação principal&lt;/span&gt;
        &lt;span class="n"&gt;reservaRepo&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;Reserva&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="na"&gt;getId&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="na"&gt;getQuarto&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="s"&gt;"CONFIRMADA"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getId&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&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;cancelarReserva&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;reservaId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// ação de compensação&lt;/span&gt;
        &lt;span class="nc"&gt;Reserva&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reservaRepo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reservaId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CANCELADA"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;reservaRepo&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;r&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// libera o quarto, etc.&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 a saga falhar no meio, o orquestrador chama &lt;code&gt;cancelarReserva&lt;/code&gt; para aquela reserva.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dica de ouro:&lt;/strong&gt; a ação de compensação &lt;strong&gt;também deve ser idempotente&lt;/strong&gt; (se chamar duas vezes, não faz bagunça). E idealmente, ela &lt;strong&gt;nunca falha&lt;/strong&gt; – mas se falhar, você entra com um processo manual ou fila de dead letter.&lt;/p&gt;




&lt;h2&gt;
  
  
  3️⃣ Idempotência – pra não repetir a mesma besteira duas vezes
&lt;/h2&gt;

&lt;p&gt;Idempotência é a chave de tudo. Se você não sabe se uma mensagem vai chegar uma ou duas vezes (e no mundo distribuído, ela vai chegar duas vezes), você precisa garantir que o efeito seja o mesmo.&lt;/p&gt;

&lt;p&gt;Como implementar: &lt;strong&gt;Idempotency Key&lt;/strong&gt; (também chamada de &lt;code&gt;requestId&lt;/code&gt; ou &lt;code&gt;transactionId&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="nd"&gt;@PostMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/pedidos"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;ResponseEntity&lt;/span&gt; &lt;span class="nf"&gt;criarPedido&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@RequestBody&lt;/span&gt; &lt;span class="nc"&gt;PedidoReq&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; 
                                  &lt;span class="nd"&gt;@RequestHeader&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Idempotency-Key"&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;idempotencyKey&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="c1"&gt;// Tabela de operações já processadas&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;processedOperationRepo&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;idempotencyKey&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Pedido&lt;/span&gt; &lt;span class="n"&gt;pedido&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;processedOperationRepo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getResult&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idempotencyKey&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;ResponseEntity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pedido&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// retorna o mesmo resultado de antes&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Processa normalmente&lt;/span&gt;
    &lt;span class="nc"&gt;Pedido&lt;/span&gt; &lt;span class="n"&gt;novoPedido&lt;/span&gt; &lt;span class="o"&gt;=&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;criar&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="c1"&gt;// Guarda a chave + resultado (na mesma transação!)&lt;/span&gt;
    &lt;span class="n"&gt;processedOperationRepo&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;ProcessedOperation&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idempotencyKey&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;novoPedido&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;ResponseEntity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;novoPedido&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;&lt;strong&gt;Boa prática:&lt;/strong&gt; a chave de idempotência é gerada &lt;strong&gt;pelo cliente&lt;/strong&gt; (UUID, por exemplo). O servidor só guarda a primeira execução. Qualquer requisição com a mesma chave retorna o resultado em cache.&lt;/p&gt;




&lt;h2&gt;
  
  
  4️⃣ Transactional Outbox – não perca mensagens (nunca mais)
&lt;/h2&gt;

&lt;p&gt;Quando você salva algo no banco &lt;strong&gt;e precisa publicar um evento&lt;/strong&gt; (tipo "PedidoCriado"), o risco é clássico: você salva no banco, mas antes de publicar o evento o serviço morre. O evento nunca vai pro Kafka, e ninguém sabe que o pedido existe.&lt;/p&gt;

&lt;p&gt;A solução é a &lt;strong&gt;tabela Outbox&lt;/strong&gt; (sim, o mesmo padrão do post anterior, mas agora aplicado dentro do serviço):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&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;id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;aggregate_id&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&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="n"&gt;event_type&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="n"&gt;JSONB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="s1"&gt;'PENDING'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Na &lt;strong&gt;mesma transação&lt;/strong&gt; do seu negócio, você insere o evento na tabela &lt;code&gt;outbox&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="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;criarPedido&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PedidoReq&lt;/span&gt; &lt;span class="n"&gt;req&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. Salva o pedido&lt;/span&gt;
    &lt;span class="n"&gt;pedidoRepo&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;pedido&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// 2. Salva o evento na outbox (MESMA transação)&lt;/span&gt;
    &lt;span class="nc"&gt;OutboxEvent&lt;/span&gt; &lt;span class="n"&gt;event&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;OutboxEvent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="no"&gt;UUID&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;randomUUID&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;pedido&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getId&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
        &lt;span class="s"&gt;"PedidoCriado"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pedido&lt;/span&gt;&lt;span class="o"&gt;)&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;event&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;Depois, um &lt;strong&gt;Message Relay&lt;/strong&gt; (pode ser um scheduler, Debezium, ou um worker) lê da tabela &lt;code&gt;outbox&lt;/code&gt; e publica no Kafka/RabbitMQ, marcando como &lt;code&gt;PUBLISHED&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="nd"&gt;@Scheduled&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fixedDelay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="o"&gt;)&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;publicarEventos&lt;/span&gt;&lt;span class="o"&gt;()&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;OutboxEvent&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;pending&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;findByStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"PENDING"&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;OutboxEvent&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pending&lt;/span&gt;&lt;span class="o"&gt;)&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="n"&gt;kafkaTemplate&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;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getTopic&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getPayload&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
            &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"PUBLISHED"&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;event&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// log e tenta de novo depois&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;&lt;strong&gt;Garantia:&lt;/strong&gt; O evento só não será publicado se o serviço morrer antes do commit da transação (mas aí o pedido também não foi salvo). Consistência garantida.&lt;/p&gt;




&lt;h2&gt;
  
  
  5️⃣ Tabela de Estado ou Log – a memória da saga
&lt;/h2&gt;

&lt;p&gt;Você precisa saber em que ponto cada saga está. Se uma saga falha, quem vai acordar e tentar compensar? Ou se um serviço ficou fora do ar, como retomar?&lt;/p&gt;

&lt;p&gt;Crie uma &lt;strong&gt;tabela de estado da saga&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;saga_state&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;saga_id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tipo&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;          &lt;span class="c1"&gt;-- ex: "CRIAR_PEDIDO"&lt;/span&gt;
    &lt;span class="n"&gt;estado&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;        &lt;span class="c1"&gt;-- STARTED, STEP1_OK, STEP2_OK, COMPLETED, FAILED&lt;/span&gt;
    &lt;span class="n"&gt;dados_payload&lt;/span&gt; &lt;span class="n"&gt;JSONB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="c1"&gt;-- contexto da saga&lt;/span&gt;
    &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;updated_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cada vez que a saga avança, você atualiza o estado (na transação local do passo atual).&lt;/p&gt;

&lt;p&gt;Se um passo falha, a saga fica em &lt;code&gt;FAILED&lt;/code&gt;. Um processo &lt;strong&gt;background&lt;/strong&gt; (ou o próprio orquestrador) pode reler sagas travadas há muito tempo e executar compensações ou retentativas.&lt;/p&gt;

&lt;p&gt;Exemplo de scheduler de monitoramento:&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;@Scheduled&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fixedDelay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60000&lt;/span&gt;&lt;span class="o"&gt;)&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;recuperarSagasTravadas&lt;/span&gt;&lt;span class="o"&gt;()&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;SagaState&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;travadas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sagaStateRepo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findByEstadoInAndUpdatedAtLessThan&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="s"&gt;"STEP1_OK"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"STEP2_OK"&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;LocalDateTime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;now&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;minusMinutes&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&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;SagaState&lt;/span&gt; &lt;span class="n"&gt;saga&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;travadas&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;warn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Saga {} travada no estado {}. Executando compensação..."&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;saga&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getId&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;saga&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getEstado&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="n"&gt;orquestrador&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;compensar&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;saga&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;h2&gt;
  
  
  🧭 Fluxo de decisão: qual técnica usar quando?
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Seu problema&lt;/th&gt;
&lt;th&gt;Técnica principal&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Coordenar vários serviços em uma operação distribuída&lt;/td&gt;
&lt;td&gt;Saga Pattern (orquestração ou coreografia)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Preciso desfazer algo que já foi feito&lt;/td&gt;
&lt;td&gt;Mecanismo de compensação (ações reversas)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;O mesmo comando pode chegar duas vezes (e isso é normal)&lt;/td&gt;
&lt;td&gt;Idempotência com chave única&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Preciso publicar eventos sem risco de perder mensagem&lt;/td&gt;
&lt;td&gt;Transactional Outbox&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Saga pode ficar travada e preciso monitorar&lt;/td&gt;
&lt;td&gt;Tabela de estado + scheduler de recuperação&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tudo isso junto&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Combão:&lt;/strong&gt; Saga orquestrada + idempotência + outbox + tabela de estado&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  ⚡ Dicas quentes que ninguém conta
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Compensação não é trivial
&lt;/h3&gt;

&lt;p&gt;Projetar uma ação reversa que &lt;strong&gt;sempre&lt;/strong&gt; funciona é difícil. Por exemplo, se você enviou um e-mail, não dá pra "desenviar". Nesse caso, a compensação pode ser enviar outro e-mail informando o cancelamento.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Idempotência tem prazo de validade
&lt;/h3&gt;

&lt;p&gt;Sua tabela de chaves de idempotência vai crescer para sempre. Crie um job que limpa chaves com mais de X dias (ex: 30 dias), a menos que você precise de histórico eterno.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Outbox com polling pode ter lag
&lt;/h3&gt;

&lt;p&gt;Se você usa um scheduler simples, seu evento pode demorar alguns segundos para ser publicado. Se precisar de latência baixa, use &lt;strong&gt;Debezium&lt;/strong&gt; (CDC) que reage quase em tempo real.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Sagas orquestradas viram ponto único?
&lt;/h3&gt;

&lt;p&gt;O orquestrador pode cair no meio. Nesse caso, você precisa que ele seja resiliente (várias instâncias, persistência de estado, e capacidade de retomar onde parou). A tabela &lt;code&gt;saga_state&lt;/code&gt; é fundamental aqui.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Coreografia é linda, mas difícil de debugar
&lt;/h3&gt;

&lt;p&gt;Quando tudo é evento, ninguém tem visão completa. Coloque &lt;strong&gt;correlation IDs&lt;/strong&gt; em cada mensagem e use um sistema de tracing (Jaeger, Zipkin) para seguir o fluxo.&lt;/p&gt;




&lt;h2&gt;
  
  
  💬 Conclusão – ACID morreu, viva a BASE (com planejamento)
&lt;/h2&gt;

&lt;p&gt;Sistemas não transacionais (bancos NoSQL, microsserviços, filas) não te dão rollback mágico. Mas isso não significa que você está condenado à inconsistência. As técnicas que vimos aqui – &lt;strong&gt;Saga Pattern, compensação, idempotência, outbox e tabela de estado&lt;/strong&gt; – são o arsenal moderno para garantir consistência eventual de forma robusta.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Transação grande?&lt;/strong&gt; Quebre em sagas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Falhou no meio?&lt;/strong&gt; Compense o que foi feito.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mensagem repetida?&lt;/strong&gt; Idempotência resolve.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Precisa publicar evento?&lt;/strong&gt; Outbox salva.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Saga travou?&lt;/strong&gt; Tabela de estado + scheduler recupera.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Agora é sua vez: já implementou alguma saga na raça? Já perdeu evento porque não usava outbox? Me conta nos comentários. E se ainda não precisou, já sabe onde mirar quando o sistema crescer. Bora codar com menos medo de consistência e mais arquitetura pensada! 🛡️🔥&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Quer aprofundar? No próximo post vou mostrar um exemplo prático completo de saga orquestrada com Spring Boot, Kafka e Debezium. Até lá!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>braziliandevs</category>
      <category>java</category>
      <category>springboot</category>
    </item>
    <item>
      <title>🎬 O Guia basico sobre Optimistic vs Pessimistic Lock no Spring Boot 🍿</title>
      <dc:creator>Fabio Rocha</dc:creator>
      <pubDate>Wed, 08 Jul 2026 22:56:09 +0000</pubDate>
      <link>https://dev.to/fabiothomazrocha/o-guia-basico-sobre-optimistic-vs-pessimistic-lock-no-spring-boot-1b4b</link>
      <guid>https://dev.to/fabiothomazrocha/o-guia-basico-sobre-optimistic-vs-pessimistic-lock-no-spring-boot-1b4b</guid>
      <description>&lt;p&gt;Você já tentou comprar o último ingresso pro filme mais hypado do ano, viu a mensagem "Reserva Confirmada", mas na hora do pagamento o sistema disse que o assento estava ocupado? Pois é. A vida de quem desenvolve sistemas de reserva é exatamente assim, mas no lugar do usuário frustrado, nós temos &lt;strong&gt;transações concorrentes&lt;/strong&gt; e o temido &lt;strong&gt;estado de corrida&lt;/strong&gt; (race condition). Vamos descobrir como o Spring Boot resolve essa briga usando &lt;strong&gt;Optimistic&lt;/strong&gt; e &lt;strong&gt;Pessimistic Locking&lt;/strong&gt;, com aquele case clássico de reserva de assentos de cinema. 🎟️&lt;/p&gt;

&lt;h3&gt;
  
  
  🔐 O Desafio: O Último Ingresso
&lt;/h3&gt;

&lt;p&gt;Imagina que temos uma tabela &lt;code&gt;SeatDetails&lt;/code&gt; com o campo &lt;code&gt;isBooked&lt;/code&gt; (status do assento). Se 100 pessoas tentarem marcar o assento &lt;code&gt;A1&lt;/code&gt; ao mesmo tempo, sem controle, várias vão ler "disponível" e tentar atualizar ao mesmo tempo. Resultado: &lt;strong&gt;sobreposição de dados&lt;/strong&gt;, ticket vendido duas vezes e uma baita dor de cabeça.&lt;/p&gt;

&lt;p&gt;Aí entram os Locking Strategies para garantir que a venda seja &lt;strong&gt;atômica e consistente&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  ⚡ 1. Optimistic Locking (O "Achismo" Otimista)
&lt;/h3&gt;

&lt;p&gt;O otimista não segura a porta. Ele deixa todo mundo tentar, mas na hora de finalizar, ele confere se alguém passou na frente.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Como funciona?&lt;/strong&gt;&lt;br&gt;
Adicionamos um campo &lt;code&gt;version&lt;/code&gt; na entidade com a anotação &lt;code&gt;@Version&lt;/code&gt;. Quando alguém atualiza o assento, o JPA incrementa a versão.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Entity&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SeatDetails&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Id&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&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="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="n"&gt;isBooked&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="nd"&gt;@Version&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// O segredo está aqui!&lt;/span&gt;
    &lt;span class="c1"&gt;// getters e setters&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Na Prática:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Usuário A e B leem o assento (versão = 1).&lt;/li&gt;
&lt;li&gt; A atualiza e commita (versão vira 2).&lt;/li&gt;
&lt;li&gt; B tenta atualizar baseado na versão 1.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Resultado:&lt;/strong&gt; O Hibernate lança um &lt;code&gt;OptimisticLockException&lt;/code&gt;. B perdeu, mas não bagunçou o banco.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Quando usar?&lt;/strong&gt; Em cenários de &lt;strong&gt;baixa concorrência&lt;/strong&gt; (edição de perfil de usuário, por exemplo).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🛡️ 2. Pessimistic Locking (O "Chato" Pragmático)
&lt;/h3&gt;

&lt;p&gt;O pessimista trava a porta na cara. Quando alguém entra para editar, ele tranca a fileira do cinema até terminar.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Como funciona?&lt;/strong&gt;&lt;br&gt;
Usamos o &lt;code&gt;@Lock&lt;/code&gt; do Spring Data JPA com o modo &lt;code&gt;PESSIMISTIC_WRITE&lt;/code&gt;. Ele manda o banco de dados executar um &lt;code&gt;SELECT ... FOR UPDATE&lt;/code&gt;, travando a linha.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;SeatRepo&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;JpaRepository&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SeatDetails&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Lock&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;LockModeType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;PESSIMISTIC_WRITE&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nd"&gt;@Query&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SELECT s FROM SeatDetails s WHERE s.id = :id"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;Optional&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SeatDetails&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;findByIdWithPessimisticLock&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@Param&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&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="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Na Prática:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Usuário A chama o método com &lt;code&gt;@Transactional&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; O banco trava a linha do assento &lt;code&gt;A1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; Usuário B tenta ler o mesmo assento e &lt;strong&gt;fica na fila de espera&lt;/strong&gt; (bloqueado) até A terminar.&lt;/li&gt;
&lt;li&gt; O &lt;code&gt;@Transactional&lt;/code&gt; garante que, ao final do processo, o lock seja liberado (commit ou rollback).&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Quando usar?&lt;/strong&gt; Em cenários de &lt;strong&gt;alta concorrência&lt;/strong&gt; e disputa feroz (leilões, Black Friday, estoque final).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🚀 3. O Novo Ator no Pedaço: RedLock com Redis (Quando o Banco Não Dá Conta do Gole)
&lt;/h3&gt;

&lt;p&gt;E quando sua aplicação já &lt;strong&gt;cresceu tanto que tem várias instâncias rodando&lt;/strong&gt; (microserviços em containers, serverless) e você precisa coordenar a reserva do assento &lt;strong&gt;entre diferentes serviços&lt;/strong&gt;, não só entre threads de uma única JVM?&lt;/p&gt;

&lt;p&gt;É aí que o banco de dados sozinho já não resolve mais. O Pessimistic Lock funciona lindamente dentro da mesma instância do banco, mas se o assento &lt;code&gt;A1&lt;/code&gt; for acessado por APIs diferentes que usam bancos diferentes? Ou se seu serviço for stateless (tipo um Lambda) e não segurar uma transação de banco aberta por muito tempo? Você precisa de um &lt;strong&gt;Distributed Lock&lt;/strong&gt;, e aí entra o &lt;strong&gt;RedLock&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Pense no RedLock como o segurança &lt;strong&gt;trancando não apenas uma porta, mas todas as saídas de emergência do cinema&lt;/strong&gt;. Ele impede que &lt;em&gt;qualquer&lt;/em&gt; instância do seu sistema, rodando em qualquer lugar, mexa no recurso ao mesmo tempo.&lt;/p&gt;

&lt;p&gt;Mas por que não usar um simples &lt;code&gt;SETNX&lt;/code&gt; do Redis? Porque se o nó master do Redis cair e você fizer failover para um Slave, &lt;strong&gt;o lock pode se perder&lt;/strong&gt;. É como se o segurança trocasse de turno no meio da briga e esquecesse quem estava com a chave. O RedLock resolve isso usando &lt;strong&gt;vários master independentes&lt;/strong&gt;. Para conseguir a chave, o cliente precisa convencer a maioria deles (mais da metade) simultaneamente. Só assim ele garante que o lock é verdadeiro e que não vai sumir.&lt;/p&gt;

&lt;p&gt;Na prática, você teria 5 masters Redis completamente separados (em máquinas diferentes). Para trancar o assento, o cliente:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Marca o horário que começou a tentar.&lt;/li&gt;
&lt;li&gt; Tenta trancar o mesmo recurso em todos os 5 masters (com um tempo de validade curto, tipo 30 segundos).&lt;/li&gt;
&lt;li&gt; Se &lt;strong&gt;conseguir trancar na maioria (pelo menos 3)&lt;/strong&gt;, o lock é dele. E o tempo que ele realmente pode usar o lock é o tempo total inicial menos o tempo que ele gastou tentando.&lt;/li&gt;
&lt;li&gt; Se falhar, ele libera os locks que conseguiu em qualquer nó e tenta de novo.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;O &lt;strong&gt;Redisson&lt;/strong&gt; (a biblioteca mais usada) faz quase tudo isso pra você. O código fica 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="c1"&gt;// Adicione no seu pom.xml&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;dependency&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;groupId&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;redisson&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;groupId&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;artifactId&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;redisson&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;artifactId&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mf"&gt;3.16&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;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;dependency&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="c1"&gt;// No seu serviço...&lt;/span&gt;
&lt;span class="nd"&gt;@Service&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ReservaService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Autowired&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;RedissonClient&lt;/span&gt; &lt;span class="n"&gt;redissonClient&lt;/span&gt;&lt;span class="o"&gt;;&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;reservarAssento&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;assentoId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;RLock&lt;/span&gt; &lt;span class="n"&gt;lock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;redissonClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getLock&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"lock:assento:"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;assentoId&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;lock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tryLock&lt;/span&gt;&lt;span class="o"&gt;())&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="c1"&gt;// Só UM cliente (ou instância) por vez entra aqui!&lt;/span&gt;
                &lt;span class="c1"&gt;// Verifica se o assento ainda está livre&lt;/span&gt;
                &lt;span class="c1"&gt;// Reserva o assento&lt;/span&gt;
                &lt;span class="c1"&gt;// Salva a reserva no banco&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;lock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;unlock&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Não esquece de liberar!&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;RuntimeException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Assento ocupado, tente novamente!"&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;&lt;strong&gt;Mas atenção!&lt;/strong&gt; O RedLock não é bala de prata. O famoso &lt;strong&gt;Martin Kleppmann&lt;/strong&gt; (autor de &lt;em&gt;Designing Data-Intensive Applications&lt;/em&gt; de onde eu tirei parte desse texto kkkk 🤫) tem uma baita crítica a ele: o RedLock assume que o relógio de todas as máquinas está perfeitamente sincronizado. No mundo real, com &lt;strong&gt;clock drift&lt;/strong&gt; (relógio atrasando ou adiantando) ou pausas longas de Garbage Collector, um lock pode expirar antes da hora, dando a impressão que o recurso está livre quando na verdade ainda está sendo usado. Se você precisa de &lt;strong&gt;correção absoluta&lt;/strong&gt; (ex: transferência bancária), o RedLock pode ser arriscado.&lt;/p&gt;

&lt;h3&gt;
  
  
  🤔 Quando Usar RedLock?
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Cenário&lt;/th&gt;
&lt;th&gt;Recomendação&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Seu sistema é uma única aplicação Spring&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Fique com Optimistic/Pessimistic Lock.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Você precisa de PERFORMANCE e tolera falhas raras&lt;/strong&gt; (ex: um job duplicado num cache)&lt;/td&gt;
&lt;td&gt;Use Redis simples com &lt;code&gt;SETNX&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Você precisa de CORREÇÃO ABSOLUTA&lt;/strong&gt; (ex: débito em conta bancária)&lt;/td&gt;
&lt;td&gt;Esqueça RedLock. Use ZooKeeper, etcd ou um banco relacional com transações fortes.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cenário híbrido: vários microserviços e precisão moderada&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;RedLock pode ser um bom caminho, mas entenda os riscos.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  📊 Tabela Rápida: Qual devo usar?
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Característica&lt;/th&gt;
&lt;th&gt;Optimistic Lock&lt;/th&gt;
&lt;th&gt;Pessimistic Lock&lt;/th&gt;
&lt;th&gt;RedLock (Redis)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Estratégia&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;"Tenta aí, se falhar, tenta de novo."&lt;/td&gt;
&lt;td&gt;"Tranca tudo, depois você mexe."&lt;/td&gt;
&lt;td&gt;"Tranca em múltiplos lugares antes de agir."&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Bloqueio&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Nenhum bloqueio enquanto lê.&lt;/td&gt;
&lt;td&gt;Bloqueio físico no banco (&lt;code&gt;SELECT FOR UPDATE&lt;/code&gt;).&lt;/td&gt;
&lt;td&gt;Usa &lt;code&gt;SET NX&lt;/code&gt; em vários masters.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Performance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Alta performance em leitura.&lt;/td&gt;
&lt;td&gt;Performance menor (espera nas filas).&lt;/td&gt;
&lt;td&gt;Rápida, mas requer ida em vários nós.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Melhor para&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Leituras freq., escritas esporádicas.&lt;/td&gt;
&lt;td&gt;Escritas freq. e muitos conflitos.&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Ambientes Distribuídos&lt;/strong&gt; (vários serviços).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Risco&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Muitas exceções se a disputa for alta.&lt;/td&gt;
&lt;td&gt;Deadlocks ou lentidão.&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Clock drift&lt;/strong&gt; e falhas de sincronização.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Código&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Anotação &lt;code&gt;@Version&lt;/code&gt; na entidade.&lt;/td&gt;
&lt;td&gt;Anotação &lt;code&gt;@Lock&lt;/code&gt; no Repository.&lt;/td&gt;
&lt;td&gt;Biblioteca &lt;strong&gt;Redisson&lt;/strong&gt; ou cliente específico.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  💡 Dica de Implementação (para não errar no código)
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Optimistic:&lt;/strong&gt; Se você receber uma &lt;code&gt;OptimisticLockException&lt;/code&gt;, é legal tratar isso na camada de serviço. Dá pra tentar uma &lt;strong&gt;retentativa automática&lt;/strong&gt; (reler os dados e tentar salvar de novo) ou avisar o usuário: &lt;em&gt;"Ops, alguém alterou esse dado. Recarregue a tela."&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Pessimistic:&lt;/strong&gt; Lembre-se sempre de rodar dentro de um método &lt;code&gt;@Transactional&lt;/code&gt;. Se você só fizer o &lt;code&gt;findByIdWithPessimisticLock&lt;/code&gt; e não tiver o &lt;code&gt;@Transactional&lt;/code&gt;, o lock morre sozinho.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  🎯 Conclusão (Na Pipoca)
&lt;/h3&gt;

&lt;p&gt;O &lt;strong&gt;Optimistic Locking&lt;/strong&gt; é perfeito para quando a briga é rara. É leve, rápido e não entope o banco. O &lt;strong&gt;Pessimistic Locking&lt;/strong&gt; é aquele segurança que trava a catraca em shows lotados: trava o banco, mas garante que ninguém vai invadir o assento. E o &lt;strong&gt;RedLock com Redis&lt;/strong&gt; é o segurança que coordena vários portões de diferentes cinemas da rede: ideal quando seus serviços estão todos espalhados, mas tem suas controvérsias e não é a bala de prata para precisão extrema.&lt;/p&gt;

&lt;p&gt;No final das contas, não existe bala de prata. A escolha depende do seu &lt;strong&gt;negócio&lt;/strong&gt;: quantas pessoas vão disputar esse assento ao mesmo tempo? Se for um filme do Pelé em dia de estreia, melhor preparar o lock e o Refri.&lt;/p&gt;

&lt;p&gt;😉 Gostou? Tem um sistema de reservas aí na sua empresa? Já usou RedLock? Conta aí nos comentários qual dessas estratégias você mais usa e se já sofreu com algum lock. 🚀&lt;/p&gt;

</description>
      <category>ai</category>
      <category>java</category>
      <category>springboot</category>
      <category>database</category>
    </item>
    <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>
