<?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>Spring Builders: Raphael De Lio</title>
    <description>The latest articles on Spring Builders by Raphael De Lio (@raphaeldelio).</description>
    <link>https://springbuilders.dev/raphaeldelio</link>
    <image>
      <url>https://springbuilders.dev/images/LiAE0GOhaaW-GkDp6YTeRWmdL5mPZmhKo0BhXs-MMPg/rs:fill:90:90/g:sm/mb:500000/ar:1/aHR0cHM6Ly9zcHJp/bmdidWlsZGVycy5k/ZXYvdXBsb2Fkcy91/c2VyL3Byb2ZpbGVf/aW1hZ2UvMzc5L2I5/NzcxMTJmLTYwZjct/NDhkNC1iYmRiLTdm/ODMyZDA5N2UxZi5q/cGc</url>
      <title>Spring Builders: Raphael De Lio</title>
      <link>https://springbuilders.dev/raphaeldelio</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://springbuilders.dev/feed/raphaeldelio"/>
    <language>en</language>
    <item>
      <title>A QA colleague asked me the difference between Lombok's @Builder and @With annotations today</title>
      <dc:creator>Raphael De Lio</dc:creator>
      <pubDate>Fri, 19 Apr 2024 15:03:15 +0000</pubDate>
      <link>https://springbuilders.dev/raphaeldelio/a-qa-colleague-asked-me-the-difference-between-lomboks-builder-and-with-annotations-2dam</link>
      <guid>https://springbuilders.dev/raphaeldelio/a-qa-colleague-asked-me-the-difference-between-lomboks-builder-and-with-annotations-2dam</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://twitter.com/raphaeldelio"&gt;Twitter&lt;/a&gt; | &lt;a href="https://www.linkedin.com/in/raphaeldelio/"&gt;LinkedIn&lt;/a&gt; | &lt;a href="https://www.youtube.com/@raphaeldelio"&gt;YouTube&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Today my team had a shortage of devs (illness, holidays), so we asked our QA colleague to help us and approve a PR so that we could've it merged in time. This PR was pretty simple, we were replacing handmade "with" methods in Java Records with Lombok's @With's annotation.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"How does @With compare to @Builder? We only used @Builder so far." &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;He asked. &lt;/p&gt;

&lt;h3&gt;
  
  
  This was my answer:
&lt;/h3&gt;

&lt;p&gt;Basically, when creating obejcts with multiple fields its better to use a builder then to use its constructor.&lt;/p&gt;

&lt;h4&gt;
  
  
  Constructor:
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Person(18, "Raphael", "Santos", "Santos", "Software Developer");
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Builder:
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Person.setAge(18).setName("Raphael").setCity("Santos").setLastName("Santos").setJob("Software Developer").build()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the second one, you can see how easier it is to identify what is being populated in the person object. In the first one you could even confuse the surname with the city, which makes it harder to maintain.&lt;/p&gt;

&lt;p&gt;Besides that, we are working with Java Records. Those are immutable objects. Once they are created, they cannot be modified.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var person1 = Person.setAge(18).setName("Raphael").setCity("Santos").setLastName("Santos").setJob("Software Developer").build()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the age has later changed to 19, I cannot modify the original object. I have to create a whole new object copying the properties from the first one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var person2 = Person.setAge(19).setName(person1.getName()).setCity(person1.getCity()).setLastName(person1.getLastName()).setJob(person1.getJob()).build();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Boring, right?&lt;/p&gt;

&lt;p&gt;And thats when @With methods come into place:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var person2 = person1.withAge(19);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All the other properties will be automatically copied. Only the age will have been changed in this case. 😃&lt;/p&gt;

&lt;p&gt;Lombok's @With annotation will take care of creating all those "with" methods for us. While the @Builder annotation will take care of applying the Builder pattern to our classes. &lt;/p&gt;

&lt;p&gt;How do you rate my explanation to him?&lt;/p&gt;

</description>
      <category>lombok</category>
      <category>java</category>
    </item>
    <item>
      <title>Chaining Kafka and Database Transactions</title>
      <dc:creator>Raphael De Lio</dc:creator>
      <pubDate>Thu, 18 Apr 2024 18:55:37 +0000</pubDate>
      <link>https://springbuilders.dev/raphaeldelio/chaining-kafka-and-database-transactions-30en</link>
      <guid>https://springbuilders.dev/raphaeldelio/chaining-kafka-and-database-transactions-30en</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://twitter.com/raphaeldelio"&gt;Twitter&lt;/a&gt; | &lt;a href="https://www.linkedin.com/in/raphaeldelio/"&gt;LinkedIn&lt;/a&gt; | &lt;a href="https://www.youtube.com/@raphaeldelio"&gt;YouTube&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In this article, we explore the complexities of handling transactions across Kafka and relational databases in Spring Boot applications.&lt;/p&gt;

&lt;p&gt;The traditional approach, using ChainedKafkaTransactionManager, has been deprecated in 2021. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://springbuilders.dev/images/d-92aH3b9LrtEWpx_YHW6X6nneEge9QhbI6Tw-AbFck/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9zcHJp/bmdidWlsZGVycy5k/ZXYvdXBsb2Fkcy9h/cnRpY2xlcy91NjZq/bTE0ejlpZXQ2OG9h/a2M4OS5wbmc" class="article-body-image-wrapper"&gt;&lt;img src="https://springbuilders.dev/images/d-92aH3b9LrtEWpx_YHW6X6nneEge9QhbI6Tw-AbFck/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9zcHJp/bmdidWlsZGVycy5k/ZXYvdXBsb2Fkcy9h/cnRpY2xlcy91NjZq/bTE0ejlpZXQ2OG9h/a2M4OS5wbmc" alt="Image description" width="424" height="44"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But worry not, the solution is elegantly simple thanks to the guidance from Gary Russell, the leading contributor to the Spring Kafka project.&lt;/p&gt;

&lt;p&gt;We'll dive into consumer-initiated transactions and see how to configure our application properly for this use case. We will also demystify what happens "Under the Boot" by observing logs to understand how transactions are managed and committed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution
&lt;/h2&gt;

&lt;p&gt;Well, the solution is simple. Fortunately, Gary Russel, the main Spring Kafka contributor, didn't leave us hanging here.&lt;/p&gt;

&lt;p&gt;In the ticket in which he deprecated the Chained Kafka Transaction Manager, he also led us to what is the recommended approach for anyone who relied on the deprecated class:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For consumer-initiated transactions, annotate the listener method with &lt;code&gt;@Transactional&lt;/code&gt;; the container (configured with a KTM) starts the kakfa transaction and the transaction interceptor starts the DB transaction. This provides virtually the same functionality as the Chained KTM in that the DB transaction will commit or roll back just before the Kafka transaction.&lt;/p&gt;

&lt;p&gt;For producer-initiated transactions, Transaction Synchronization already works; if another transaction is in process when the transactional KafkaTemplate is called, the template will synchronize the Kafka transaction with the existing transaction.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And he also left &lt;a href="https://github.com/spring-projects/spring-kafka/blob/ad5e754bf1c0b9c2566fe1ab900d33467ae07dd9/spring-kafka-docs/src/main/antora/modules/ROOT/pages/tips.adoc#examples-of-kafka-transactions-with-other-transaction-managers"&gt;documentation&lt;/a&gt; in the repository with examples on how to implement it. The solution is simple.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enable Kafka Transaction Manager
&lt;/h3&gt;

&lt;p&gt;First of all, we need to enable Spring Kafka's Kafka Transaction Manager. We can do it by simply setting the transactional id property in our &lt;code&gt;application.properties&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;spring.kafka.producer.transaction-id-prefix&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;tx-&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By setting this property, Spring Boot will automatically configure the Kafka Transaction Manager. (&lt;a href="https://github.com/spring-projects/spring-kafka/blob/ad5e754bf1c0b9c2566fe1ab900d33467ae07dd9/spring-kafka-docs/src/main/antora/modules/ROOT/pages/kafka/transactions.adoc"&gt;Documentation&lt;/a&gt;)&lt;/p&gt;

&lt;h3&gt;
  
  
  Configure your Listener Containers to use Kafka Transaction Manager
&lt;/h3&gt;

&lt;p&gt;Spring Boot will also configure our listener containers to use the auto configured &lt;code&gt;KafkaTransactionManager&lt;/code&gt;. This will ensure that our listeners will automatically begin a Kafka transaction when receiving a new message.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using the @Transactional Annotation
&lt;/h3&gt;

&lt;p&gt;Here, when implementing our listener, we must ensure we are also annotating the method with the &lt;code&gt;@Transactional&lt;/code&gt; annotation. This annotation will tell our application to begin a JPA transaction upon receiving a message.&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;@KafkaListener&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="s"&gt;"group1"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;topics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"topic1"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@Transactional&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"transactionManager"&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;listen1&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;in&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;"Received from topic1: {}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;in&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;"Sending to topic2: {}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toUpperCase&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="s"&gt;"topic2"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toUpperCase&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;"Writing to database: {}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;demoRepository&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="nc"&gt;DemoEntity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&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="n"&gt;in&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;timestamp&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;currentTimeMillis&lt;/span&gt;&lt;span class="o"&gt;())&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="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The trick here is giving transactionManager as the value for the &lt;code&gt;@Transactional&lt;/code&gt; annotation. This is because there will be two transaction managers available: &lt;code&gt;transactionManager&lt;/code&gt; and &lt;code&gt;kafkaTransactionManager&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;transactionManager&lt;/code&gt; bean is an instance of JpaTransactionManager while the &lt;code&gt;kafkaTransactionManager&lt;/code&gt; bean is an instance of KafkaTransactionManager.&lt;/p&gt;

&lt;p&gt;And the reason why we want to give the &lt;code&gt;transactionManager&lt;/code&gt; as the value for the &lt;code&gt;@Transactional&lt;/code&gt; annotation is because our &lt;code&gt;KafkaMessageListenerContainer&lt;/code&gt; is already creating transactions for us on consuption. Whenever a new message comes in, it will automatically begin a Kafka transaction before it starts running our method.&lt;/p&gt;

&lt;p&gt;Therefore, all we have to do is tell Spring Boot to, before our method is run, to also begin a transaction, but at this time, for the &lt;code&gt;JpaTransactionManager&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  And that’s it!
&lt;/h3&gt;

&lt;p&gt;There’s nothing else we have to do. When receiving new messages, Spring Kafka will automatically begin a Kafka Transaction and after that, the &lt;code&gt;@Transactional&lt;/code&gt; annotation will create a JPA Transaction. If the JPA Transaction fails, the Kafka Transaction will also fail and be rolled back.&lt;/p&gt;

&lt;h2&gt;
  
  
  Under the Boot
&lt;/h2&gt;

&lt;p&gt;Let’s not just take my word for granted. Let’s also look at the logs under different scenarios to see what’s actually going on in our application.&lt;/p&gt;

&lt;p&gt;First of all, let’s tell Spring Boot to change the logging level of a few classes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;logging&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;org&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;apache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;kafka&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;error&lt;/span&gt;
        &lt;span class="na"&gt;orm&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;jpa&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;JpaTransactionManager&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;trace&lt;/span&gt;
        &lt;span class="na"&gt;kafka&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;transaction&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;trace&lt;/span&gt;
          &lt;span class="na"&gt;listener&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;KafkaMessageListenerContainer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;error&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we want to mute some noise generated by the &lt;code&gt;org.springframework.kafka.listener.KafkaMessageListenerContainer&lt;/code&gt; class and the &lt;code&gt;org.apache.kafka&lt;/code&gt; package. We do it by setting them to &lt;code&gt;error&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then, we want to actually see more information about the &lt;code&gt;JpaTransactionManager&lt;/code&gt; class and the &lt;code&gt;org.springframework.transaction&lt;/code&gt; and the &lt;code&gt;org.springframework.kafka.transaction&lt;/code&gt; packages. Therefore, we set them to trace.&lt;/p&gt;

&lt;p&gt;Now, when our application gets started, we want to send a message to Kafka:&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;@Bean&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;ApplicationRunner&lt;/span&gt; &lt;span class="nf"&gt;sendKafkaMessage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;KafkaTemplate&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;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;template&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="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;executeInTransaction&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;t&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="s"&gt;"topic1"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"test"&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;This will trigger our listener method and we will be able to see what’s actually going on under the “Boot”.&lt;/p&gt;

&lt;h3&gt;
  
  
  @Transactional(“transactionManager”)
&lt;/h3&gt;

&lt;p&gt;Let’s try first by using the &lt;code&gt;transactionManager&lt;/code&gt; as the value of our &lt;code&gt;@Transactional&lt;/code&gt; annotation.&lt;/p&gt;

&lt;p&gt;These are the logs that were generated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;14:28:33.833 DEBUG o.s.k.t.KafkaTransactionManager - Creating new transaction with name [null]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
14:28:33.834 DEBUG o.s.k.t.KafkaTransactionManager - Created Kafka transaction on producer [CloseSafeProducer [delegate=org.apache.kafka.clients.producer.KafkaProducer@685b500f]]
14:28:33.839 DEBUG o.s.o.j.JpaTransactionManager - Creating new transaction with name [demo.kafka.demokafkatransaction.DemoListener.listen1]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; 'transactionManager'
14:28:33.839 DEBUG o.s.o.j.JpaTransactionManager - Opened new EntityManager [SessionImpl(151636314&amp;lt;open&amp;gt;)] for JPA transaction
14:28:33.842 DEBUG o.s.o.j.JpaTransactionManager - Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@611e4b08]
14:28:33.842 INFO  d.k.d.DemoListener - Received from topic1: test
14:28:33.842 INFO  d.k.d.DemoListener - Sending to topic2: TEST
14:28:33.847 INFO  d.k.d.DemoListener - Writing to database: test
14:28:33.848 DEBUG o.s.o.j.JpaTransactionManager - Found thread-bound EntityManager [SessionImpl(151636314&amp;lt;open&amp;gt;)] for JPA transaction
14:28:33.848 DEBUG o.s.o.j.JpaTransactionManager - Participating in existing transaction
14:28:33.856 DEBUG o.s.o.j.JpaTransactionManager - Initiating transaction commit
14:28:33.856 DEBUG o.s.o.j.JpaTransactionManager - Committing JPA transaction on EntityManager [SessionImpl(151636314&amp;lt;open&amp;gt;)]
14:28:33.868 DEBUG o.s.o.j.JpaTransactionManager - Closing JPA EntityManager [SessionImpl(151636314&amp;lt;open&amp;gt;)] after transaction
14:28:33.986 DEBUG o.s.k.t.KafkaTransactionManager - Initiating transaction commit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see that our logs start with &lt;code&gt;KafkaTransactionManager&lt;/code&gt; creating a new Kafka Transaction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;KafkaTransactionManager - Creating new transaction with name [null]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Right after the Kafka Transaction is created, the &lt;code&gt;JpaTransactionManager&lt;/code&gt; creates a Database Transaction as well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;JpaTransactionManager - Creating new transaction with name [demo.kafka.demokafkatransaction.DemoListener.listen1]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; 'transactionManager'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only after the Database Transaction is created, we will see our actual implementation being run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DemoListener - Received from topic1: test
DemoListener - Sending to topic2: TEST
DemoListener - Writing to database: test
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As we start writing into the Database, we can see that the &lt;code&gt;JpaTransactionManager&lt;/code&gt; discovers that a Database Transaction is already running and that it will take part into this transaction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;JpaTransactionManager - Participating in existing transaction
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For each operation in the database we would see a line like the one above. And this is important because we want all of the operations to run within the same transaction.&lt;/p&gt;

&lt;p&gt;As we reach the end of our method, we will see that the &lt;code&gt;JpaTransactionManager&lt;/code&gt; will init the commit of its transaction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;JpaTransactionManager - Initiating transaction commit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And right after that, the &lt;code&gt;KafkaTransactionManager&lt;/code&gt; will also commit its own transaction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;KafkaTransactionManager - Initiating transaction commit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s it. We can through the logs that our Kafka Transaction is wrapping the Database Transaction. It starts before the Database Transaction starts and is commited after the Database Transaction is commited.&lt;/p&gt;

&lt;h3&gt;
  
  
  @Transactional(“kafkaTransactionManager”)
&lt;/h3&gt;

&lt;p&gt;Now let’s see what would happen if instead we give our &lt;code&gt;@Transactional&lt;/code&gt; annotation the &lt;code&gt;KafkaTransactionManager&lt;/code&gt; as its value.&lt;/p&gt;

&lt;p&gt;Once again, let’s analyze the generated logs after we run our application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;14:30:29.741 DEBUG o.s.k.t.KafkaTransactionManager - Creating new transaction with name [null]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
14:30:29.742 DEBUG o.s.k.t.KafkaTransactionManager - Created Kafka transaction on producer [CloseSafeProducer [delegate=org.apache.kafka.clients.producer.KafkaProducer@f8fd0f4]]
14:30:29.751 DEBUG o.s.k.t.KafkaTransactionManager - Participating in existing transaction
14:30:29.751 INFO  d.k.d.DemoListener - Received from topic1: test
14:30:29.751 INFO  d.k.d.DemoListener - Sending to topic2: TEST
14:30:29.754 INFO  d.k.d.DemoListener - Writing to database: test
14:30:29.757 DEBUG o.s.o.j.JpaTransactionManager - Creating new transaction with name [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
14:30:29.757 DEBUG o.s.o.j.JpaTransactionManager - Opened new EntityManager [SessionImpl(530662483&amp;lt;open&amp;gt;)] for JPA transaction
14:30:29.759 DEBUG o.s.o.j.JpaTransactionManager - Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@74c4c7d0]
14:30:29.767 DEBUG o.s.o.j.JpaTransactionManager - Initiating transaction commit
14:30:29.767 DEBUG o.s.o.j.JpaTransactionManager - Committing JPA transaction on EntityManager [SessionImpl(530662483&amp;lt;open&amp;gt;)]
14:30:29.780 DEBUG o.s.o.j.JpaTransactionManager - Closing JPA EntityManager [SessionImpl(530662483&amp;lt;open&amp;gt;)] after transaction
14:30:29.895 DEBUG o.s.k.t.KafkaTransactionManager - Initiating transaction commit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our logs start once again with KafkaTransactionManager creating a new transaction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;KafkaTransactionManager - Creating new transaction with name [null]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, we can already see a difference right after the Kafka Transaction is created. This time, the KafkaTransactionManager says that it’s participating in an existing transaction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;KafkaTransactionManager - Participating in existing transaction
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is because our Kafka Transaction was first created by the &lt;code&gt;KafkaMessageListenerContainer&lt;/code&gt;. After it’s creation, the &lt;code&gt;@Transactional&lt;/code&gt; annotation tries to create another transaction, but fortunately, it identified that a transaction was already active and decided to participate in the active one.&lt;/p&gt;

&lt;p&gt;Then, we can also see that no Database Transaction is created. Instead, our implementation starts executing right after the Kafka Transaction is created:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DemoListener - Received from topic1: test
DemoListener - Sending to topic2: TEST
DemoListener - Writing to database: test
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And only once our implementation tries to write into the database, a Database Transaction is created:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;JpaTransactionManager - Creating new transaction with name [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the following lines of code are very important because we can see that it is not our implementation that is triggering the creation of a Database Transaction, it is the &lt;code&gt;save()&lt;/code&gt; method from the &lt;code&gt;JpaRepository&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And it’s creating an actual transaction instead of participating in an existing one.&lt;/strong&gt; This means that this will be commited as soon as the &lt;code&gt;save()&lt;/code&gt; method is completed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;JpaTransactionManager - Initiating transaction commit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we were doing multiple operations in the database, we would see a new Database Transaction being created and commited for each one of them.&lt;/p&gt;

&lt;p&gt;And finally, after all Database Transaction have been commited. We see that the &lt;code&gt;KafkaTransactionManager&lt;/code&gt; commits its transaction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;KafkaTransactionManager - Initiating transaction commit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Even though the Kafka Transaction would have failed if any of the Database Transactions failed, the previous successful Database Transactions wouldn’t have rolled back. This is not the behavior we are looking for.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In conclusion, handling distributed transactions in a microservice architecture backed by Apache Kafka and Spring Boot doesn’t have to be a daunting task. Though the deprecation of &lt;code&gt;ChainedKafkaTransactionManager&lt;/code&gt; may have initially created confusion, the solutions provided by Spring Kafka offer a streamlined approach to &lt;em&gt;&lt;strong&gt;likely&lt;/strong&gt;&lt;/em&gt; achieve atomicity across both Kafka and database transactions.&lt;/p&gt;

&lt;p&gt;By configuring a few properties and using the &lt;code&gt;@Transactional&lt;/code&gt; annotation wisely, we can ensure that the Kafka transaction is only committed if the database transaction is also committed.&lt;/p&gt;

&lt;p&gt;In my next story, I want to explore the Transactional Outbox Pattern, a pattern designed to ensure even stronger guarantees for transactional consistency in a distributed environment. Stay tuned!&lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/raphaeldelio/demo-kafka-transaction"&gt;https://github.com/raphaeldelio/demo-kafka-transaction&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Source
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/spring-projects/spring-kafka/issues/1699"&gt;https://github.com/spring-projects/spring-kafka/issues/1699&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/spring-projects/spring-kafka/blob/ad5e754bf1c0b9c2566fe1ab900d33467ae07dd9/spring-kafka-docs/src/main/antora/modules/ROOT/pages/tips.adoc#examples-of-kafka-transactions-with-other-transaction-managers"&gt;Examples of Kafka Transactions with Other Transaction Managers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/spring-projects/spring-kafka/blob/ad5e754bf1c0b9c2566fe1ab900d33467ae07dd9/spring-kafka-docs/src/main/antora/modules/ROOT/pages/kafka/transactions.adoc"&gt;https://github.com/spring-projects/spring-kafka/blob/ad5e754bf1c0b9c2566fe1ab900d33467ae07dd9/spring-kafka-docs/src/main/antora/modules/ROOT/pages/kafka/transactions.adoc&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.spring.io/spring-framework/reference/data-access/transaction/declarative/annotations.html"&gt;https://docs.spring.io/spring-framework/reference/data-access/transaction/declarative/annotations.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>springkafka</category>
      <category>springboot</category>
    </item>
  </channel>
</rss>
