<?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: Ankit Sood</title>
    <description>The latest articles on DEV Community by Ankit Sood (@ankit_sood_66861d56d8e42a).</description>
    <link>https://dev.to/ankit_sood_66861d56d8e42a</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1691281%2F48cba708-a638-44de-aa6a-4daee55d844b.jpg</url>
      <title>DEV Community: Ankit Sood</title>
      <link>https://dev.to/ankit_sood_66861d56d8e42a</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ankit_sood_66861d56d8e42a"/>
    <language>en</language>
    <item>
      <title>Why I Stopped Using Lombok’s `@Data` on JPA Entities</title>
      <dc:creator>Ankit Sood</dc:creator>
      <pubDate>Tue, 14 Apr 2026 18:20:21 +0000</pubDate>
      <link>https://dev.to/ankit_sood_66861d56d8e42a/why-i-stopped-using-lomboks-data-on-jpa-entities-2nga</link>
      <guid>https://dev.to/ankit_sood_66861d56d8e42a/why-i-stopped-using-lomboks-data-on-jpa-entities-2nga</guid>
      <description>&lt;p&gt;A few days ago, I ran into a bug that didn’t look like a bug at all.&lt;/p&gt;

&lt;p&gt;I was working on a fairly standard Spring Boot service, nothing unusual. A couple of entities, some relationships, and a small feature change. To keep things concise, I did what most of us do:&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;@Data&lt;/span&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;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;It felt natural, clean and efficient.&lt;/p&gt;

&lt;p&gt;The application started fine. APIs were working. Everything looked normal.&lt;/p&gt;

&lt;p&gt;Until I added a simple log statement.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Bug That Didn’t Make Sense
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&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;"User details: {}"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The moment this line executed, the application crashed with a &lt;code&gt;StackOverflowError&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;At first glance, it didn’t add up.&lt;/p&gt;

&lt;p&gt;There was no recursion in my code. No complex logic. Just a log statement.&lt;/p&gt;

&lt;p&gt;But as I traced through the stack, the pattern became clear.&lt;/p&gt;




&lt;h2&gt;
  
  
  When &lt;code&gt;toString()&lt;/code&gt; Becomes a Trap
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;User&lt;/code&gt; entity had a bidirectional relationship:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;User&lt;/code&gt; had a collection of &lt;code&gt;Order&lt;/code&gt;s&lt;/li&gt;
&lt;li&gt;Each &lt;code&gt;Order&lt;/code&gt; referenced back to the &lt;code&gt;User&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a very common JPA pattern.&lt;/p&gt;

&lt;p&gt;What I hadn’t paid attention to was what Lombok’s &lt;code&gt;@Data&lt;/code&gt; had generated for me.&lt;/p&gt;

&lt;p&gt;It includes a &lt;code&gt;toString()&lt;/code&gt; method that prints &lt;strong&gt;all fields&lt;/strong&gt;, including relationships.&lt;/p&gt;

&lt;p&gt;So when I logged the &lt;code&gt;User&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;User.toString()&lt;/code&gt; tried to print &lt;code&gt;orders&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Each &lt;code&gt;Order&lt;/code&gt; tried to print its &lt;code&gt;user&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Which again tried to print &lt;code&gt;orders&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And so on.&lt;/p&gt;

&lt;p&gt;An infinite loop—triggered by logging.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Subtle Performance Issue
&lt;/h2&gt;

&lt;p&gt;While investigating, I noticed something else that was even more concerning.&lt;/p&gt;

&lt;p&gt;In another part of the code, I had something like:&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;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;users&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;HashSet&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing unusual.&lt;/p&gt;

&lt;p&gt;But it was unexpectedly slow.&lt;/p&gt;

&lt;p&gt;The reason, again, traced back to &lt;code&gt;@Data&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It generates &lt;code&gt;equals()&lt;/code&gt; and &lt;code&gt;hashCode()&lt;/code&gt; using all fields. In a JPA entity, that can be problematic especially when those fields include lazily loaded relationships.&lt;/p&gt;

&lt;p&gt;To compute the hash, Hibernate ended up initializing those lazy fields, which meant:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Additional database queries&lt;/li&gt;
&lt;li&gt;More data loaded than expected&lt;/li&gt;
&lt;li&gt;All happening implicitly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No explicit query. No clear signal in the code.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Core Issue
&lt;/h2&gt;

&lt;p&gt;The root of the problem is simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JPA entities are not plain Java objects.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;They are managed by a persistence context, often proxied, and frequently only partially loaded.&lt;/p&gt;

&lt;p&gt;Lombok, however, generates code as if everything is a simple POJO. That disconnect is where these issues originate.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Changed
&lt;/h2&gt;

&lt;p&gt;I didn’t stop using Lombok. But I stopped using &lt;code&gt;@Data&lt;/code&gt; on entities.&lt;/p&gt;

&lt;p&gt;Instead, I now prefer:&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;@Getter&lt;/span&gt;
&lt;span class="nd"&gt;@Setter&lt;/span&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;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;For methods like &lt;code&gt;toString()&lt;/code&gt;, &lt;code&gt;equals()&lt;/code&gt;, and &lt;code&gt;hashCode()&lt;/code&gt;, I either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Write them explicitly, or&lt;/li&gt;
&lt;li&gt;Use Lombok annotations selectively (e.g., excluding relationships)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This keeps behavior predictable and avoids unintended side effects.&lt;/p&gt;




&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;@Data&lt;/code&gt; is incredibly useful in the right places.&lt;/p&gt;

&lt;p&gt;But JPA entities are not one of them.&lt;/p&gt;

&lt;p&gt;What looks like a small convenience can introduce:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Infinite recursion&lt;/li&gt;
&lt;li&gt;Hidden database queries&lt;/li&gt;
&lt;li&gt;Hard-to-debug behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All without any obvious warning.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thought
&lt;/h2&gt;

&lt;p&gt;This wasn’t a complex failure. It didn’t require scale or load to surface.&lt;/p&gt;

&lt;p&gt;It came from a perfectly normal development flow and that’s exactly why it’s worth paying attention to.&lt;/p&gt;

&lt;p&gt;Sometimes the most dangerous problems aren’t the ones we struggle to write. They’re the ones we don’t realize we’ve already written for us.&lt;/p&gt;

&lt;h2&gt;
  
  
  📚 Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Vlad Mihalcea — How to implement equals and hashCode using the JPA entity identifier&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://vladmihalcea.com/hibernate-facts-equals-and-hashcode/" rel="noopener noreferrer"&gt;https://vladmihalcea.com/hibernate-facts-equals-and-hashcode/&lt;/a&gt;&lt;br&gt;&lt;br&gt;
A must-read on why JPA entity equality should be based on identifiers rather than all fields, and how improper implementations can lead to subtle bugs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Project Lombok — &lt;a class="mentioned-user" href="https://dev.to/data"&gt;@data&lt;/a&gt; Annotation Documentation&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://projectlombok.org/features/Data" rel="noopener noreferrer"&gt;https://projectlombok.org/features/Data&lt;/a&gt;&lt;br&gt;&lt;br&gt;
Explains what &lt;code&gt;@Data&lt;/code&gt; actually generates under the hood, which helps understand why its default behavior can conflict with JPA entities.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>java</category>
      <category>springboot</category>
      <category>hibernate</category>
      <category>jpa</category>
    </item>
    <item>
      <title>Performance Testing Databases the Right Way: A Real-World Engineering Journey</title>
      <dc:creator>Ankit Sood</dc:creator>
      <pubDate>Thu, 20 Nov 2025 11:49:04 +0000</pubDate>
      <link>https://dev.to/ankit_sood_66861d56d8e42a/performance-testing-databases-the-right-way-a-real-world-engineering-journey-4mfi</link>
      <guid>https://dev.to/ankit_sood_66861d56d8e42a/performance-testing-databases-the-right-way-a-real-world-engineering-journey-4mfi</guid>
      <description>&lt;p&gt;A few months ago, I found myself facing one of those architectural decisions you can’t take lightly: Which database should power our application?&lt;/p&gt;

&lt;p&gt;On paper, several options looked great. Azure SQL had strong consistency and a familiar relational model. Google Spanner promised global scale and near-infinite horizontal growth. But the same problem kept bothering me:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;None of the available benchmarks reflected my application’s workload.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every benchmark I found was synthetic. Every article was based on someone else’s traffic patterns. Every vendor claim assumed a workload that looked nothing like ours.&lt;/p&gt;

&lt;p&gt;And that’s when it clicked.&lt;/p&gt;

&lt;p&gt;If I wanted real answers, I needed to simulate our actual read/write behavior, our transaction mix, our concurrency pattern, our connection pooling.&lt;/p&gt;

&lt;p&gt;I needed a test setup that behaved like our Java application—not a generic stress tester.This article is the story of how I modeled the workloads before I even touched Azure SQL or Google Spanner.&lt;/p&gt;

&lt;p&gt;In Part 2, I’ll share what happened when I finally put those databases under load.&lt;/p&gt;




&lt;h3&gt;
  
  
  Why I Couldn’t Trust Generic Database Benchmarks
&lt;/h3&gt;

&lt;p&gt;At first, I thought I’d simply compare p95 latencies or look up TPS numbers for each database. But it didn’t take long to realize the flaws:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;These numbers came from engineered environments.&lt;/li&gt;
&lt;li&gt;They assumed ideal network conditions.&lt;/li&gt;
&lt;li&gt;And they definitely didn’t match our schema or access patterns.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most importantly?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Our system was not a synthetic benchmark.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It had:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;specific read/write ratios&lt;/li&gt;
&lt;li&gt;certain frequently accessed tables&lt;/li&gt;
&lt;li&gt;particular update patterns&lt;/li&gt;
&lt;li&gt;a real connection pool&lt;/li&gt;
&lt;li&gt;and lock-sensitive flows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I stopped searching for benchmarks and started designing tests that mirrored our workload.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 1: Defining What “Good Performance” Actually Meant
&lt;/h3&gt;

&lt;p&gt;Before modeling anything in JMeter, I forced myself to answer a simple question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What does good database performance look like for this application?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This gave me the baseline metrics and below is the "Performance criteria" I defined:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;p95 and p99 response time targets&lt;/li&gt;
&lt;li&gt;Expected throughput (operations per second)&lt;/li&gt;
&lt;li&gt;Resource utilization boundaries (CPU, memory, IOPS)&lt;/li&gt;
&lt;li&gt;Acceptable error rate&lt;/li&gt;
&lt;li&gt;Scalability expectations under increased concurrency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These weren’t arbitrary numbers—they were tied to real SLAs and user expectations. With this foundation, I could work backwards and ensure the workloads I modeled aligned with the actual business needs.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 2: Understanding the Application’s Actual Database Behaviour
&lt;/h3&gt;

&lt;p&gt;Next, I analyzed our application’s schema and data-access patterns.&lt;/p&gt;

&lt;p&gt;I listed every operation the application performed frequently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reads (SELECT)&lt;/li&gt;
&lt;li&gt;Writes (INSERT)&lt;/li&gt;
&lt;li&gt;Updates (UPDATE)&lt;/li&gt;
&lt;li&gt;Deletes (DELETE)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But I didn’t stop there. I also identified:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;which tables were read-heavy&lt;/li&gt;
&lt;li&gt;which operations were latency-sensitive&lt;/li&gt;
&lt;li&gt;which queries created row-level locks&lt;/li&gt;
&lt;li&gt;which flows needed strict consistency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This step was crucial because performance testing is not just about “running queries fast.” It’s about understanding how those queries behave under real concurrency.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 3: Classifying Operations by Frequency and Importance
&lt;/h3&gt;

&lt;p&gt;One of the biggest mistakes in performance testing is treating all operations equally. Real applications never have a perfectly even CRUD distribution.&lt;/p&gt;

&lt;p&gt;Some flows run thousands of times per minute and others just a few.&lt;br&gt;
So, I categorized operations based on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;frequency&lt;/li&gt;
&lt;li&gt;business criticality&lt;/li&gt;
&lt;li&gt;concurrency sensitivity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This gave me clarity on how to weight each operation in the workload.&lt;/p&gt;


&lt;h3&gt;
  
  
  Step 4: Designing Realistic Workload Scenarios
&lt;/h3&gt;

&lt;p&gt;Now came the core of the modeling process.&lt;/p&gt;

&lt;p&gt;I created workload mixes that reflected how our application behaves in production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scenario 1: Read-Heavy Workload&lt;/strong&gt;&lt;br&gt;
Most user-facing apps are read-dominant. For ours, this looked like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;70% reads  
20% writes  
10% updates  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Scenario 2: Balanced Workload&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For internal workflows and batch processes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;50% reads  
30% writes  
10% updates  
10% deletes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each scenario represented a realistic slice of our system’s behaviour not a synthetic stress test.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 5: Designing the JMeter Setup to Behave Like a Java Application
&lt;/h3&gt;

&lt;p&gt;This part was non-negotiable for me. I didn’t just want JMeter to hit the database, I wanted it to behave like our Java service.&lt;/p&gt;

&lt;p&gt;So I built the test plan with care:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Separate thread groups for each CRUD operation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This allowed me to configure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;individual concurrency control&lt;/li&gt;
&lt;li&gt;precise latency measurement&lt;/li&gt;
&lt;li&gt;different ramp-up patterns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Parameterized SQL queries&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Using CSV Data Set Config, every iteration pulled dynamic values.&lt;br&gt;
and this avoided caching effects and mimicked real traffic variation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shared JDBC connection pool (1:1 ratio)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;All thread groups used the same JDBC pool, just like production. This created:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;connection contention&lt;/li&gt;
&lt;li&gt;realistic queueing&lt;/li&gt;
&lt;li&gt;lock waits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Overall, true concurrency behaviour.&lt;/p&gt;

&lt;p&gt;At this point, JMeter wasn’t just “sending traffic.” It was simulating the exact way our application interacted with the database.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Workload Modeling Was Complete — Now the Real Testing Could Begin
&lt;/h3&gt;

&lt;p&gt;After several iterations, reviews, and dry runs, I finally had a performance test suite that felt authentic. It matched:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;our query patterns&lt;/li&gt;
&lt;li&gt;our connection pool settings&lt;/li&gt;
&lt;li&gt;our concurrency behaviour&lt;/li&gt;
&lt;li&gt;our read/write ratios&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And most importantly, it reflected the pressure our application would put on any database—whether Azure SQL, Google Spanner, or something else entirely.&lt;/p&gt;

&lt;p&gt;Now I was ready for the real showdown.&lt;/p&gt;




&lt;h3&gt;
  
  
  Coming Up Next: Azure SQL vs Google Spanner — The Actual Results
&lt;/h3&gt;

&lt;p&gt;In Part 2, I’ll share how both databases performed under the exact workloads modeled in this article. And trust me the results were nothing like the vendor benchmarks.&lt;/p&gt;

&lt;p&gt;I’ll cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;p95/p99 latencies&lt;/li&gt;
&lt;li&gt;read/write throughput&lt;/li&gt;
&lt;li&gt;locking and contention behaviour&lt;/li&gt;
&lt;li&gt;resource consumption&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and which database ultimately won for our use case&lt;/p&gt;

&lt;p&gt;Stay tuned for Part 2.&lt;/p&gt;

</description>
      <category>database</category>
      <category>jmeter</category>
      <category>performance</category>
    </item>
    <item>
      <title>Building Custom HTTP Request Metrics in Spring Boot: A Deep Dive into Observability</title>
      <dc:creator>Ankit Sood</dc:creator>
      <pubDate>Thu, 18 Sep 2025 12:15:48 +0000</pubDate>
      <link>https://dev.to/ankit_sood_66861d56d8e42a/building-custom-http-request-metrics-in-spring-boot-a-deep-dive-into-observability-3j9o</link>
      <guid>https://dev.to/ankit_sood_66861d56d8e42a/building-custom-http-request-metrics-in-spring-boot-a-deep-dive-into-observability-3j9o</guid>
      <description>&lt;p&gt;Modern APIs often serve multiple clients i.e mobile apps, partner services, internal tools and Knowing who is calling you and how often is critical for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Capacity planning&lt;/strong&gt;: scale before peak traffic hits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fair usage &amp;amp; billing&lt;/strong&gt;: detect high-volume consumers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alerting &amp;amp; SLOs&lt;/strong&gt;: raise alarms when specific clients misbehave.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s build a zero-dependency filter that tracks request counts by HTTP method and a custom consumer header and exports those metrics through Micrometer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture at a Glance
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────┐      ┌────────────────────┐      ┌────────────┐
│  Client  │ ───▶ │Custom MetricsFilter│ ───▶ │ Controller │
└──────────┘      └────────────────────┘      └────────────┘
                      │
                      ▼
               Micrometer Registry
                      │
                      ▼
         Prometheus / Datadog / CloudWatch …

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our solution uses a Spring Boot servlet filter that intercepts every HTTP request and records metrics using Micrometer.&lt;/p&gt;

&lt;p&gt;Flow looks like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Request in → Filter → Controller&lt;/li&gt;
&lt;li&gt;Filter → Micrometer Counter with tags for method and consumer.&lt;/li&gt;
&lt;li&gt;Metrics scraped or pushed to your observability backend.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, let's go through the building process step by step:&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Add Micrometer
&lt;/h3&gt;

&lt;p&gt;Spring Boot 3+ includes Micrometer by default when you use the spring-boot-starter-actuator.&lt;br&gt;
For Prometheus:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
  &amp;lt;groupId&amp;gt;io.micrometer&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;micrometer-registry-prometheus&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enable endpoints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;management.endpoints.web.exposure.include=health,metrics,prometheus
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: The Filter Code
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Component
@Order(1)
public class CustomHttpMetricsFilter extends OncePerRequestFilter {

    private static final ConcurrentHashMap&amp;lt;String, Counter&amp;gt; COUNTERS = new ConcurrentHashMap&amp;lt;&amp;gt;();
    private static final String METRIC = "http_requests";

    @Autowired
    private MeterRegistry registry;

    @Override
    protected void doFilterInternal(HttpServletRequest req,
                                    HttpServletResponse res,
                                    FilterChain chain)
            throws ServletException, IOException {

        incrementCounter(req);
        chain.doFilter(req, res);
    }

    private void incrementCounter(HttpServletRequest req) {
        String consumer = req.getHeader("CONSUMER.ID");
        if (consumer == null) consumer = "null";

        String key = req.getMethod() + ":" + consumer;

        COUNTERS.compute(key, (k, existing) -&amp;gt; {
            Counter c = existing;
            if (c == null) {
                c = Counter.builder(METRIC)
                           .tag("method", req.getMethod())
                           .tag("consumer", consumer)
                           .description("Per-consumer HTTP request count")
                           .register(registry);
            }
            c.increment();
            return c;
        });
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key points&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;OncePerRequestFilter&lt;/code&gt; guarantees one execution per HTTP request.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ConcurrentHashMap&lt;/code&gt; and compute makes thread-safe cache of Counter objects.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Tags&lt;/code&gt; method and consumer make each counter unique and queryable.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 3: Observe the Metrics
&lt;/h3&gt;

&lt;p&gt;Run the app and hit it with a few GET/POST requests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -H "CONSUMER.ID: mobile-app" http://localhost:8080/api/...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and now visit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://localhost:8080/actuator/prometheus
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We should see the following in the response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http_requests_total{method="GET", consumer="mobile-app"} 42.0
http_requests_total{method="POST", consumer="partner-service"} 5.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;strong&gt;Why Not Use @Timed?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Micrometer provides @Timed for controller methods, but that’s per-endpoint and doesn’t automatically group by a custom header.&lt;br&gt;
Our filter gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cross-cutting coverage: every request, even for static resources or error pages.&lt;/li&gt;
&lt;li&gt;Dynamic tags: any header or attribute can become a label.&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;&lt;strong&gt;Some Production Tips &amp;amp; Enhancements&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Label Cardinality&lt;br&gt;
Too many unique consumer IDs can lead to huge time series.&lt;br&gt;
Solution: restrict IDs to a known set or hash/anonymize them.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Metrics Cleanup is required as counter objects never shrink.&lt;br&gt;
Solution: implement TTL eviction or register only for known consumers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Latency &amp;amp; Status Codes&lt;br&gt;
To measure request duration and tags for HTTP status add a timer .&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Security&lt;br&gt;
Expose /actuator/prometheus only to your metrics scraper.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Testing&lt;br&gt;
Use MockMvc and assert&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;h3&gt;
  
  
  Final Thoughts
&lt;/h3&gt;

&lt;p&gt;With fewer than 50 lines of code, you get real-time, per-consumer traffic visibility. Whether you’re debugging a sudden spike or preparing for a product launch, these metrics become invaluable.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“You can’t improve what you don’t measure.”&lt;br&gt;
Start measuring today—your future self will thank you.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>springboot</category>
      <category>java</category>
      <category>micrometer</category>
      <category>observability</category>
    </item>
    <item>
      <title>🚀 Slow queries on Cosmos DB ?Here’s How I Fixed Pain with Deep Pagination &amp; Cross-Partition Queries</title>
      <dc:creator>Ankit Sood</dc:creator>
      <pubDate>Sat, 09 Aug 2025 13:22:50 +0000</pubDate>
      <link>https://dev.to/ankit_sood_66861d56d8e42a/slow-queries-on-cosmos-db-heres-how-i-fixed-pain-with-deep-pagination-cross-partition-queries-34k6</link>
      <guid>https://dev.to/ankit_sood_66861d56d8e42a/slow-queries-on-cosmos-db-heres-how-i-fixed-pain-with-deep-pagination-cross-partition-queries-34k6</guid>
      <description>&lt;p&gt;If you have ever paginated through thousands of records in Azure Cosmos DB (MongoDB API) and felt your queries are slow, painfully slow, you’re not alone.&lt;/p&gt;

&lt;p&gt;This happened to me recently.&lt;/p&gt;

&lt;p&gt;I had a collection with a partitioned on invoiceId, but my queries were filtering on purchase Order Number and combination of vendorNumber &amp;amp; createDate.&lt;/p&gt;

&lt;p&gt;The result? The first few pages were fine. But by page 10+, response time exploded.&lt;/p&gt;

&lt;p&gt;Before jumping on to what was going on and how I fixed it, quick starter on what Cosmos DB is and how it works.&lt;/p&gt;




&lt;h2&gt;
  
  
  Cosmos DB Introduction
&lt;/h2&gt;

&lt;p&gt;Azure Cosmos DB is Microsoft’s globally distributed, multi-model database service.&lt;/p&gt;

&lt;p&gt;The MongoDB API mode means Cosmos DB will present itself as if you were connecting to a MongoDB server. Hence, your MongoDB drivers, tools, and queries mostly work as-is, but data is stored in Cosmos DB’s underlying storage engine. &lt;/p&gt;

&lt;p&gt;Think of it as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A MongoDB-compatible skin over Cosmos DB’s distributed, serverless, elastic infrastructure.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now, let's discuss how Cosmos DB stores the data.&lt;/p&gt;

&lt;p&gt;When you create a collection/container in Cosmos DB, you must pick a partition key (e.g., /invoiceId).&lt;br&gt;
Cosmos DB then:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hashes the value of the partition key for each document&lt;/li&gt;
&lt;li&gt;Stores the document in a physical and logical partition based on that hash&lt;/li&gt;
&lt;li&gt;Each physical partition has its own storage and throughput budget&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All docs with the same invoiceId go to the same partition&lt;/li&gt;
&lt;li&gt;Different invoiceId values may go to different partitions&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Why It Matters ?
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Query by Partition Key = Laser Target 🎯
&lt;/h3&gt;

&lt;p&gt;If you query using the partition key, Cosmos DB knows exactly which single physical partition has your data.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
Partition key = &lt;code&gt;/invoiceId&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invoices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;u123&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here’s what happens internally:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Cosmos DB hashes "u123" → finds Partition #7.&lt;/li&gt;
&lt;li&gt;It queries only Partition #7.&lt;/li&gt;
&lt;li&gt;You get the result quickly and cheaply.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Low RU cost (minimal data scanned)&lt;/li&gt;
&lt;li&gt;Fast (single partition)&lt;/li&gt;
&lt;li&gt;No fan-out (doesn’t hit every partition)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Cross-Partition Query = Scatter &amp;amp; Gather 🍂
&lt;/h3&gt;

&lt;p&gt;If you don’t provide the partition key in your filter — or you filter by a field that isn’t the partition key — Cosmos DB must check all partitions.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
Partition key = &lt;code&gt;/invoiceId&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invoices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Paid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What happens internally:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Cosmos DB doesn’t know which partition contains matching docs.&lt;/li&gt;
&lt;li&gt;Sends the query to all partitions in parallel (scatter).&lt;/li&gt;
&lt;li&gt;Collects results from each (gather).&lt;/li&gt;
&lt;li&gt;Merges them, applies sorting, etc.&lt;/li&gt;
&lt;li&gt;Returns the final set.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Drawbacks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Higher RU cost (every partition processes the query)&lt;/li&gt;
&lt;li&gt;Slower (parallel calls + merge step)&lt;/li&gt;
&lt;li&gt;Possible page-by-page retrieval if result is large&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqyibho909dqzk3jh8ge1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqyibho909dqzk3jh8ge1.png" alt="CosmosWorking" width="670" height="560"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Real-World Scenario
&lt;/h2&gt;

&lt;p&gt;Suppose you have documents like this, with a partition key on &lt;code&gt;invoiceId&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;invoiceId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;890&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;source&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POS&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vendorNumber&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;invoiceNumber&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;INV-123&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;destStoreNbr&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;country&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;IND&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;totalAmt&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;21.76&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;createDate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1970-01-01T00:14:05.691Z&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;updateDate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1970-01-01T00:14:05.691Z&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ❌ The Problem
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Our clients wanted to search invoices by: 

&lt;ol&gt;
&lt;li&gt;Invoice Number&lt;/li&gt;
&lt;li&gt;Vendor Number + Date&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;The primary collection was partitioned for &lt;strong&gt;write scalability&lt;/strong&gt;, not for search queries.&lt;/li&gt;
&lt;li&gt;Even worse, a vendor number + date could have over &lt;strong&gt;10,000 invoice IDs in a single day&lt;/strong&gt; in some cases.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Why were these search patterns a problem ?
&lt;/h4&gt;

&lt;p&gt;All these searches were resulting in the usecases mentioned below: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cross-partition&lt;/strong&gt; queries forcing database to scan across multiple partitions (slower by design)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deep pagination using skip and limit&lt;/strong&gt; forcing database to skip thousands of docs before returning required page (skip + limit problem). Potentially resulting in RU throttling if too much data is requested.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;It’s like flipping to page 10,000 in a book by reading every page before it. Inefficient, right?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Hence, we needed a way to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Make these queries fast and cheap.&lt;/li&gt;
&lt;li&gt;Avoid schema duplication or extra writes in the request path. &lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  ✅ Solution
&lt;/h3&gt;

&lt;p&gt;Instead of querying the primary collection directly, we created a Lookup Collection with ahead of time processing. You can think of it as a small, precomputed “routing table” for searches.&lt;/p&gt;

&lt;p&gt;What’s in the Lookup Collection?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Type = inv → maps invoiceNumber → invoiceId (with partition key)&lt;/li&gt;
&lt;li&gt;Type = vendorNumber → maps vendorNumber + date → multiple invoiceIds&lt;/li&gt;
&lt;li&gt;A single lookup doc contains at most 1000 invoice IDs. 

&lt;ul&gt;
&lt;li&gt;If more exist, we create multiple docs for the same vendor/date with a count field to track pagination.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;TTL = time to live for each lookup document.&lt;/li&gt;

&lt;/ul&gt;




&lt;h3&gt;
  
  
  ⚙️ The Architecture
&lt;/h3&gt;

&lt;p&gt;The solution has 4 different components:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Primary Collection&lt;/strong&gt;: 

&lt;ul&gt;
&lt;li&gt;It holds all invoice documents. &lt;/li&gt;
&lt;li&gt;It is partitioned for writes, not for queries.&lt;/li&gt;
&lt;li&gt;Acts as the source of truth.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Change Data Capture (CDC):&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;It listens to inserts only in the primary collection.&lt;/li&gt;
&lt;li&gt;Publishes those inserts to Kafka.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lookup Consumers&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Two independent java deployments running with their own Kafka consumer groups one for each type of search pattern.

&lt;ul&gt;
&lt;li&gt;Invoice Lookup Consumer → creates type="inv" docs.&lt;/li&gt;
&lt;li&gt;Vendor Lookup Consumer → creates type="vendorNumber" docs, with pagination logic.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Both writing to same lookup collection.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API Search Flow&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Search by invoice number:

&lt;ul&gt;
&lt;li&gt;Query lookup collection for type="inv" and prtnKey=invoiceNumber.&lt;/li&gt;
&lt;li&gt;If found → query primary collection with the exact partition key.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Search by vendor number + date:

&lt;ul&gt;
&lt;li&gt;Query lookup collection for type="vendorNumber" with pagination.&lt;/li&gt;
&lt;li&gt;Gather invoice IDs → query primary collection with partition keys.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;This ensures every primary collection query has the partition key → no cross-partition scans.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsysim90r0u788n9v3kzi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsysim90r0u788n9v3kzi.png" alt="Lookup Ingestion" width="760" height="470"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sample Invoice mapping document:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;_id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;6370cfb35934c7afdcffbb6f&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;prtnKey&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;INV-123&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;inv&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;invIds&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="mi"&gt;890&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ttl&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;252288000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sample Vendor mapping document:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;_id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;6370cfb35934c7afdcffbb6a&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;prtnKey&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;10&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;createdDate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1754739742&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vendorNumber&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;invIds&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="mi"&gt;890&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="mi"&gt;891&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="mi"&gt;892&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="mi"&gt;893&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;count&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ttl&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;252288000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In both the documents shared above, &lt;code&gt;prtnKey&lt;/code&gt; is the partition key for the lookup collection and &lt;code&gt;type&lt;/code&gt; signified which value is stored as part of the &lt;code&gt;prtnKey&lt;/code&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  📃 Why this works ?
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criteria&lt;/th&gt;
&lt;th&gt;Results&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Speed&lt;/td&gt;
&lt;td&gt;Lookup docs are tiny and indexed for lightning-fast reads.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scalability&lt;/td&gt;
&lt;td&gt;CDC keeps lookup data fresh without affecting write throughput.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flexibility&lt;/td&gt;
&lt;td&gt;Adding a new search type? Just add a new lookup doc type.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pagination&lt;/td&gt;
&lt;td&gt;The 1000-ID limit per doc avoids monster documents and RU spikes.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Deep pagination and cross-partition queries can cripple performance in Cosmos DB. The good news? With smarter strategies and thoughtful data modeling, you can keep queries fast—even at scale.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Have you faced similar issues? Share your experience or tips in the comments!&lt;/p&gt;

</description>
      <category>cosmosdb</category>
      <category>database</category>
      <category>dataengineering</category>
      <category>mongodb</category>
    </item>
    <item>
      <title>Demystifying Java Records: A Developer's Guide</title>
      <dc:creator>Ankit Sood</dc:creator>
      <pubDate>Sun, 03 Aug 2025 06:53:29 +0000</pubDate>
      <link>https://dev.to/ankit_sood_66861d56d8e42a/demystifying-java-records-a-developers-guide-h0d</link>
      <guid>https://dev.to/ankit_sood_66861d56d8e42a/demystifying-java-records-a-developers-guide-h0d</guid>
      <description>&lt;p&gt;Oracle has released the Java version 24 this year, but still I see the usage of the Java Records (release in Java 16) is limited. This article will be focussed on Java Records what it does and where we can use it. Lets dive into it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Based on JEP:359, Records are kind of type declarations and is a restricted form of class. It gives up the freedom that classes usually enjoy and in return, gain a significant degree of concision. Below code sample tells you, how you can create a record.&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="n"&gt;record&lt;/span&gt; &lt;span class="nf"&gt;Person&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;name&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;age&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;A record acquires many standard members automatically, like:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A &lt;code&gt;private final&lt;/code&gt; field for each component of the state description.&lt;/li&gt;
&lt;li&gt;A public read accessor method for each component of the state description, with the same name and type as the component.&lt;/li&gt;
&lt;li&gt;A public constructor, whose signature is the same as the state description, which initialises each field from the corresponding argument.&lt;/li&gt;
&lt;li&gt;Implementations of &lt;code&gt;equals&lt;/code&gt; and &lt;code&gt;hashCode&lt;/code&gt; that say two records are equal if they are of the same type and contain the same state.&lt;/li&gt;
&lt;li&gt;An implementation of &lt;code&gt;toString&lt;/code&gt; that includes the string representation of all the record components, with their names.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Characteristics
&lt;/h2&gt;

&lt;p&gt;Let's go through some of these characteristics one by one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🔒 Fields of Records are private and final by Default&lt;/strong&gt;&lt;br&gt;
In Java Records, all fields are implicitly declared as private and final. This means that once the values are set via the canonical constructor, they cannot be changed. Also, since the fields are private, they cannot be accessed directly from outside the record. Instead, you access them via the automatically generated accessor methods.&lt;/p&gt;

&lt;p&gt;This immutability is one of the key traits of records, making them a perfect choice for modeling data carriers or value objects.&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="n"&gt;record&lt;/span&gt; &lt;span class="nf"&gt;Person&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;name&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// No need to explicitly declare fields, getters, or constructor&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;💡 Internally, this is roughly equivalent to:&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;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Person&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;final&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;name&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;final&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;Person&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;name&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="o"&gt;)&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;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&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;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;age&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="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;name&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;name&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="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;age&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;age&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;As you can see, the name and age fields are private final, and you get read-only access through the generated methods name() and age().&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;🔁 equals(), hashCode(), and toString() are Generated by Default&lt;/strong&gt;&lt;br&gt;
One of the most convenient features of Java Records is that they automatically generate implementations for the equals(), hashCode(), and toString() methods based on all the record fields.&lt;/p&gt;

&lt;p&gt;This makes records ideal for use cases like data transfer objects or value types, where equality is based on content rather than identity — and saves you from writing a lot of boilerplate code manually.&lt;/p&gt;

&lt;p&gt;🧠 Behind the Scenes, compiler automatically provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;equals() — Compares all fields for equality&lt;/li&gt;
&lt;li&gt;hashCode() — Computes hash based on all fields&lt;/li&gt;
&lt;li&gt;toString() — Generates a string like Person[name=John, age=30]&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No need to override any of these unless you want to customize the behaviour, records handle it for you out of the box.&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;🚫 Records Can't Participate in Data Hierarchies&lt;/strong&gt;&lt;br&gt;
Java Records cannot extend other classes, including other records. This is because records implicitly extend java.lang.Record, and Java does not support multiple inheritance for classes.&lt;/p&gt;

&lt;p&gt;This restriction ensures that records remain simple, immutable data carriers without the complexities of inheritance hierarchies.&lt;/p&gt;

&lt;p&gt;✅ You can implement interfaces:&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;interface&lt;/span&gt; &lt;span class="nc"&gt;Identifiable&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;id&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="n"&gt;record&lt;/span&gt; &lt;span class="nf"&gt;User&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;id&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;name&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;Identifiable&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;❌ You cannot extend another class or record:&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;Base&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ❌ This will not compile&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="nf"&gt;Derived&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;id&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Base&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;Why this restriction?&lt;/strong&gt;&lt;br&gt;
Allowing records to participate in inheritance hierarchies would contradict their design goals of simplicity, transparency, and immutability. They're intended to be final, shallow, and self-contained data holders, not flexible object-oriented components.&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;📦 Records Can Be Declared Inline&lt;/strong&gt;&lt;br&gt;
Since Java 16, records can also be declared inline (i.e., as local or nested classes) within methods, constructors, or other blocks of code. This is useful when you need a simple data carrier only within a limited scope — for example, as a helper during data transformation or aggregation.&lt;/p&gt;

&lt;p&gt;This feature promotes cleaner code without polluting the top-level class or package namespace.&lt;/p&gt;

&lt;p&gt;Example: &lt;br&gt;
✅ Declaring a record inside a method&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;OrderService&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;processOrder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="nf"&gt;OrderItem&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;name&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;quantity&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{}&lt;/span&gt;

        &lt;span class="nc"&gt;OrderItem&lt;/span&gt; &lt;span class="n"&gt;item&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;OrderItem&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Book"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&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;"Processing item: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;item&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;⛔ Scope Limitation&lt;br&gt;
Inline/local records are only visible within the block/method they’re defined in. This is very useful for temporary data structures that are not reused elsewhere.&lt;/p&gt;

&lt;p&gt;💡 When to Use&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Grouping temporary fields in data processing&lt;/li&gt;
&lt;li&gt;Returning lightweight objects from helper methods&lt;/li&gt;
&lt;li&gt;Avoiding boilerplate for short-lived data carriers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This feature helps to use the full power of records even for short-lived and context-specific use cases.&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;✨ Records Can Use Compact Constructors&lt;/strong&gt;&lt;br&gt;
Java Records support a special form of constructor called a compact constructor, which allows you to add validation or custom logic without repeating the assignment of fields.&lt;/p&gt;

&lt;p&gt;In compact constructors, you don’t need to explicitly assign parameters to fields — the compiler does it for you automatically. You just focus on preconditions or side effects.&lt;/p&gt;

&lt;p&gt;✅ Example: Validating Fields in a Compact Constructor&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="n"&gt;record&lt;/span&gt; &lt;span class="nf"&gt;Product&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;name&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;price&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="nc"&gt;Product&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;price&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;)&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;IllegalArgumentException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Price cannot be negative"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;strip&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// You can also transform fields&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;🔍 What’s Happening Here?&lt;br&gt;
The compiler automatically generates:&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="k"&gt;this&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;name&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;price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;after the custom code block runs.&lt;/p&gt;

&lt;p&gt;This makes the code cleaner compared to explicitly writing a full canonical constructor.&lt;/p&gt;

&lt;p&gt;❗ Restrictions&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can’t change the fields directly (they’re final)&lt;/li&gt;
&lt;li&gt;You can modify or validate parameters before the assignment happens&lt;/li&gt;
&lt;li&gt;You must use the parameter names exactly as declared in the record header&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This makes the java records much more than just “dumb data holders” and allows to enforce invariants and transformations cleanly and concisely.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;🟰 Records Use Data Equality, Not Reference Equality&lt;/strong&gt;&lt;br&gt;
Unlike regular Java classes (where equals() by default compares references), records are designed to represent data, so they use data (value-based) equality out of the box.&lt;/p&gt;

&lt;p&gt;This means two record instances are considered equal if all their fields are equal, even if they're different objects in memory.&lt;/p&gt;

&lt;p&gt;✅ Example:&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="n"&gt;record&lt;/span&gt; &lt;span class="nf"&gt;Point&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;y&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;class&lt;/span&gt; &lt;span class="nc"&gt;Demo&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;main&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;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Point&lt;/span&gt; &lt;span class="n"&gt;p1&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;Point&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;Point&lt;/span&gt; &lt;span class="n"&gt;p2&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;Point&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&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;p1&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;p2&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;        &lt;span class="c1"&gt;// false (reference equality)&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;p1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p2&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;   &lt;span class="c1"&gt;// true  (data equality)&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;💡 What's Going On?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"==" checks if p1 and p2 are the same object → returns false&lt;/li&gt;
&lt;li&gt;.equals() (auto-generated by the record) checks if all fields match → returns true&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🚀 Why This Matters ?&lt;br&gt;
This behaviour makes records ideal for use cases where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You care about what data an object holds, not which instance it is.&lt;/li&gt;
&lt;li&gt;You need reliable comparisons in collections like Set, Map, or during testing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So with records, you get true value semantics just like data classes in Kotlin or case classes in Scala.&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;🔐 Records Are Strongly Immutable, Even Reflection Can't Modify Them&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Unlike regular classes where immutability is often conventional (you just don’t provide setters), with records it is enforced by the JVM.&lt;/p&gt;

&lt;p&gt;All fields in a record are private, final and assigned once via the constructor. Even the reflection which can typically bypass access modifiers can't modify record fields, unless you use JVM-breaking hacks like Unsafe or deep instrumentation.&lt;/p&gt;

&lt;p&gt;✅ Example:&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="n"&gt;record&lt;/span&gt; &lt;span class="nf"&gt;User&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;username&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;age&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;🧪 Trying to Break with Reflection:&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="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.lang.reflect.Field&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;class&lt;/span&gt; &lt;span class="nc"&gt;Demo&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;main&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;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="n"&gt;user&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;User&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Ankit"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;Field&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getDeclaredField&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"age"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setAccessible&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="n"&gt;field&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;user&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// Throws IllegalAccessException or InaccessibleObjectException&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;💥 Result is an exception like:&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="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;IllegalAccessException&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Cannot&lt;/span&gt; &lt;span class="n"&gt;modify&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or, on newer JVMs:&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="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;reflect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;InaccessibleObjectException&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🚫 Why This Matters&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ensures data integrity&lt;/li&gt;
&lt;li&gt;Safer to use in &lt;strong&gt;multi-threaded environments&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Makes records ideal for cache keys, messages, DTOs, and functional programming patterns&lt;/li&gt;
&lt;li&gt;With records, immutability isn’t just a best practice. It’s a guarantee baked into the language and runtime.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 "Hope this article helps other developers get started with Java Records. Let me know what you think. Have you used records in production yet?"&lt;/p&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://openjdk.org/jeps/359" rel="noopener noreferrer"&gt;JEP-359&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>java</category>
      <category>cleancode</category>
      <category>softwaredevelopment</category>
      <category>backenddevelopment</category>
    </item>
    <item>
      <title>🚀 How We Preserved Event Order in Kafka While Streaming From Cosmos DB: A Real-World Journey</title>
      <dc:creator>Ankit Sood</dc:creator>
      <pubDate>Sat, 02 Aug 2025 16:57:25 +0000</pubDate>
      <link>https://dev.to/ankit_sood_66861d56d8e42a/how-we-preserved-event-order-in-kafka-while-streaming-from-cosmos-db-a-real-world-journey-50hf</link>
      <guid>https://dev.to/ankit_sood_66861d56d8e42a/how-we-preserved-event-order-in-kafka-while-streaming-from-cosmos-db-a-real-world-journey-50hf</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Just publish the event after saving to the database. How hard can it be?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Famous last words. 😅&lt;/p&gt;

&lt;p&gt;What started as a simple requirement in one of my projects quickly turned into a deep dive on &lt;strong&gt;ordering guarantees&lt;/strong&gt;, &lt;strong&gt;idempotence&lt;/strong&gt;, and the &lt;strong&gt;quirks of using Cosmos DB’s Mongo API&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is the story of how we used the Outbox Pattern, Spring Boot, and Apache Kafka to build a resilient system that preserved strict ordering of events—even in the face of failures.&lt;/p&gt;

&lt;h3&gt;
  
  
  🎯 Goal
&lt;/h3&gt;

&lt;p&gt;We were building an accounts payable backend on Azure:&lt;/p&gt;

&lt;p&gt;📦 Cosmos DB (Mongo API) for storing invoices.&lt;br&gt;
🔥 Apache Kafka as the backbone for event-driven workflows.&lt;br&gt;
🌱 Spring Boot for application logic.&lt;/p&gt;


&lt;h3&gt;
  
  
  🎯 Requirements
&lt;/h3&gt;

&lt;p&gt;✅ When a user places an order, save it to the database&lt;br&gt;
✅ Immediately publish an OrderConfirmed event to Kafka&lt;br&gt;
✅ Ensure downstream consumers always process events in order&lt;/p&gt;

&lt;p&gt;Sounds simple enough… right?&lt;/p&gt;


&lt;h3&gt;
  
  
  🎯 Approaches
&lt;/h3&gt;

&lt;p&gt;😅 The First Naive Attempt and Here’s how we started:&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;// 1. Save order&lt;/span&gt;
&lt;span class="n"&gt;mongoTemplate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;insert&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 2. Publish to Kafka&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;"order-events"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;This worked for some time But pretty soon, things blew up.&lt;/p&gt;

&lt;p&gt;🔥 &lt;strong&gt;Problem #1: Lost Events&lt;/strong&gt;&lt;br&gt;
If Kafka was down after the DB write, the event was lost forever. Downstream services never found out about the order.&lt;/p&gt;

&lt;p&gt;🔥 &lt;strong&gt;Problem #2: Out-of-Order Delivery&lt;/strong&gt;&lt;br&gt;
Even with retries, we saw scenarios like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OrderShipped(order-123) arrives BEFORE OrderConfirmed(order-123)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This broke downstream systems relying on proper sequencing.&lt;/p&gt;

&lt;p&gt;🧩 &lt;strong&gt;Enter the Outbox Pattern to save the day&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of trying to send events directly to Kafka, we:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Extended our DB schema with an outbox collection&lt;/li&gt;
&lt;li&gt;When saving an order, also saved a pending outbox event in the same transaction&lt;/li&gt;
&lt;li&gt;Built a background publisher to read outbox events and send them to Kafka in order&lt;/li&gt;
&lt;li&gt;Marked events as "SENT" only after Kafka acknowledged&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here’s the flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[App writes Order + Outbox event] → [Outbox Processor] → [Kafka]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;📦 &lt;strong&gt;Outbox pattern&lt;/strong&gt; acts as a reliable buffer between your DB and Kafka. This pattern works well with any SQL Database but doesn't work with Cosmos DB mongo API.&lt;/p&gt;

&lt;h3&gt;
  
  
  ⚡ Challenges With Cosmos DB Mongo API
&lt;/h3&gt;

&lt;p&gt;We knew Cosmos DB’s Mongo API isn’t MongoDB and Transactions only work if:&lt;br&gt;
✅ Both writes are in the same collection&lt;br&gt;
✅ Documents share the same partitionKey&lt;/p&gt;

&lt;p&gt;So we designed a shared collection:&lt;/p&gt;

&lt;p&gt;🗄 &lt;strong&gt;Structure of Documents&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Order Document
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"order-123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"order"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"userId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user-456"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"confirmed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"partitionKey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user-456"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Outbox Event Document
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"event-789"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"event"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"eventType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"OrderConfirmed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"payload"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"orderId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"order-123"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"createdAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-06-27T12:00:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PENDING"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"partitionKey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user-456"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Both uses the same partitionKey to enable transactions.&lt;/p&gt;
&lt;h2&gt;
  
  
  💻 The Implementation:
&lt;/h2&gt;

&lt;p&gt;We have used Java and Spring boot to implement this. Below is how we put it all together:&lt;/p&gt;

&lt;p&gt;🏗 &lt;strong&gt;Atomic Write Service&lt;/strong&gt;&lt;br&gt;
We ensured order + outbox event writes were atomic:&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;@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;OrderService&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;MongoTemplate&lt;/span&gt; &lt;span class="n"&gt;mongoTemplate&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;createOrderWithEvent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Order&lt;/span&gt; &lt;span class="n"&gt;order&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="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;mongoTemplate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;insert&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"sharedCollection"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;mongoTemplate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;insert&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="s"&gt;"sharedCollection"&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;Kafka Outbox Publisher&lt;/strong&gt;&lt;br&gt;
A scheduled Spring component polls pending outbox events and sends them to Kafka in creation order.&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;@Component&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;OutboxEventPublisher&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;MongoTemplate&lt;/span&gt; &lt;span class="n"&gt;mongoTemplate&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;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;kafkaTemplate&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&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;1000&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;publishEvents&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Query&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Query&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Criteria&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="s"&gt;"type"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;is&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"event"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;and&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"status"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;is&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="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Sort&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;by&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"createdAt"&lt;/span&gt;&lt;span class="o"&gt;)).&lt;/span&gt;&lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&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;events&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mongoTemplate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;find&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;OutboxEvent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"sharedCollection"&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;events&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="s"&gt;"order-events"&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;getPartitionKey&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="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

                &lt;span class="c1"&gt;// Mark as SENT&lt;/span&gt;
                &lt;span class="n"&gt;mongoTemplate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;updateFirst&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                    &lt;span class="nc"&gt;Query&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Criteria&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="s"&gt;"_id"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;is&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;getId&lt;/span&gt;&lt;span class="o"&gt;())),&lt;/span&gt;
                    &lt;span class="nc"&gt;Update&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;update&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"status"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"SENT"&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
                    &lt;span class="s"&gt;"sharedCollection"&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;"Published event: "&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;getId&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="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;err&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;"Kafka send failed for event: "&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;getId&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Stop to avoid out-of-order sends&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;Kafka Producer Configuration&lt;/strong&gt;&lt;br&gt;
We enabled idempotence and sync sends to preserve order:&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="n"&gt;props&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ProducerConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ENABLE_IDEMPOTENCE_CONFIG&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="n"&gt;props&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ProducerConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ACKS_CONFIG&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"all"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;props&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ProducerConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🏗 &lt;strong&gt;Transaction Manager Configuration&lt;/strong&gt;&lt;br&gt;
Spring’s @Transactional is not same as Transaction for Cosmos or Mongo.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;It's designed for relational databases (JPA, JDBC, etc.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It Relies on a transaction manager like PlatformTransactionManager&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For MongoDB, it needs MongoDB 4.x+ with replica sets and Spring Data MongoDB transaction support.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&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="nc"&gt;MongoTransactionManager&lt;/span&gt; &lt;span class="nf"&gt;transactionManager&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MongoDatabaseFactory&lt;/span&gt; &lt;span class="n"&gt;dbFactory&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="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;MongoTransactionManager&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dbFactory&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;To use MongoDB transactions via Spring Boot, all the below things must be true:&lt;br&gt;
✅ MongoDB server version ≥ 4.0&lt;br&gt;
✅ Connected to a replica set (Cosmos DB Mongo API v4.0+ does support transactions but only under certain conditions)&lt;br&gt;
✅ Use MongoTransactionManager explicitly in Spring&lt;/p&gt;

&lt;p&gt;But Cosmos DB Mongo API:&lt;br&gt;
⚠ Only supports transactions within the same partition (documents must share partitionKey)&lt;br&gt;
⚠ Has subtle differences from native MongoDB transactions&lt;/p&gt;

&lt;p&gt;In case, a transaction should be started explicitly, we can use&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="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ClientSession&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mongoTemplate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getMongoDbFactory&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getMongoClient&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;startSession&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;startTransaction&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;mongoTemplate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withSession&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;session&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;insert&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"sharedCollection"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;mongoTemplate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withSession&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;session&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;insert&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="s"&gt;"sharedCollection"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;commitTransaction&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;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;abortTransaction&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;throw&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="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🎯 Final Thoughts
&lt;/h3&gt;

&lt;p&gt;The Outbox Pattern helped us bridge the gap between Cosmos DB and Kafka. It’s a robust, production-ready solution for ordered, reliable event streaming—even when your DB doesn’t support native CDC. &lt;/p&gt;

&lt;p&gt;CDC was one more approach that we could have used. In the past, we have used cosmos change stream which had its own issues and hence we decided to go ahead with this.&lt;/p&gt;

&lt;p&gt;👉 Have you faced ordering challenges in Kafka pipelines? How did you solve them? Share your experiences below!&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/ankit-sood/mongo-outbox-app/tree/main" rel="noopener noreferrer"&gt;Source Code&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cosmosdb</category>
      <category>eventdriven</category>
      <category>springboot</category>
      <category>outbox</category>
    </item>
    <item>
      <title>Getting Started with Kafka Connect: A Comprehensive Guide</title>
      <dc:creator>Ankit Sood</dc:creator>
      <pubDate>Sun, 16 Feb 2025 17:53:49 +0000</pubDate>
      <link>https://dev.to/ankit_sood_66861d56d8e42a/getting-started-with-kafka-connect-a-comprehensive-guide-4k91</link>
      <guid>https://dev.to/ankit_sood_66861d56d8e42a/getting-started-with-kafka-connect-a-comprehensive-guide-4k91</guid>
      <description>&lt;p&gt;This article will be focussed on understanding what Kafka Connect is, what it does and how to run this in any environment. Inspiration for this article was my struggle to identify how to run the Kafka Connect with out using the Confluent Cloud as most of the articles on the web either uses Confluent Cloud or a shell script to start the connector. I wanted to create a Java application which can use the open source Kafka Connect jar and can be hosted like any other FAT jar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Source Code
&lt;/h2&gt;

&lt;p&gt;If you would like to try it by yourself or you want to go through my source code, you can clone my &lt;a href="https://github.com/ankit-sood/kafka-connect-standalone" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt; and go through the readme file. So, Let's begin.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;We will be using Java as primary Language and Maven as the dependency management solution. So, to run this you'll need atleast Java 17 and Maven 3.9 installed in your system. In case you don't have Java or maven installed, you can download and install it by using below links.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.azul.com/downloads/?package=jdk#zulu" rel="noopener noreferrer"&gt;JDK&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://maven.apache.org/download.cgi" rel="noopener noreferrer"&gt;Maven&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'll running Kafka locally in my system. If you want to know how to setup Kafka Locally, please use this &lt;a href="https://kafka.apache.org/quickstart" rel="noopener noreferrer"&gt;link&lt;/a&gt; to set it up. Also, I am using &lt;a href="https://github.com/obsidiandynamics/kafdrop" rel="noopener noreferrer"&gt;Kafdrop&lt;/a&gt; to view Kafka brokers, topics, messages, consumer groups, and ACLs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Kafka Connect is a tool that helps move data between Apache Kafka and other systems in a reliable and scalable way. It makes it easy to set up connectors that transfer large amounts of data in and out of Kafka. You can use it to bring in data from entire databases or collect metrics from servers into Kafka topics for real-time processing. It also allows exporting data from Kafka to storage, query systems, or batch processing for offline analysis. Kafka Connect stores different types of information regarding the connector i.e. configuration of the connector, working status of the connector and last committed offset of the connector.&lt;/p&gt;

&lt;p&gt;Kafka Connect currently supports two modes of execution: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Standalone(single process)&lt;/li&gt;
&lt;li&gt;Distributed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In Standalone mode, all work is performed in a single process and the details related to configuration, offset and status is maintained in a file. This configuration is simpler to setup and get started with and may be useful in situations where only one worker makes sense (e.g. collecting log files), but it does not benefit from some of the features of Kafka Connect such as fault tolerance. &lt;/p&gt;

&lt;p&gt;In Distributed mode, Kafka Connect stores the offsets, configs and task statuses in Kafka topics. It is recommended to manually create the topics for offset, configs and statuses in order to achieve the desired the number of partitions and replication factors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;p&gt;For the purpose of this exercise, we will just be running our Kafka Connect in &lt;strong&gt;distributed mode&lt;/strong&gt;. Hence, we will create our offset, config and status topics in advance. We will use script given below to create these.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;connect-offsets:&lt;/strong&gt; Topic is used for storing offsets. This topic should have many partitions and be replicated and compacted.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# offset.storage.topic
bin/kafka-topics.sh --create --bootstrap-server localhost:9092 --topic connect-offsets --replication-factor 3 --partitions 1 --config cleanup.policy=compact
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;connect-configs:&lt;/strong&gt; Topic is used for storing connector and task configurations; note that this should be a single partition, highly replicated, and compacted topic.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# config.storage.topic
bin/kafka-topics.sh --create --bootstrap-server localhost:9092 --topic connect-configs --replication-factor 1 --partitions 1 --config cleanup.policy=compact
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;connect-status:&lt;/strong&gt; Topic to use for storing statuses. This topic can have multiple partitions and should be replicated and compacted.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# status.storage.topic
bin/kafka-topics.sh --create --bootstrap-server localhost:9092 --topic connect-status --replication-factor 1 --partitions 10 --config cleanup.policy=compact
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3al7ktsij2nwojb4zaof.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3al7ktsij2nwojb4zaof.png" alt="Configuration-Topics" width="800" height="588"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can see in the above screenshot that the required topics are created. Next step would be to create a sample java project. I'll be using my InteliJ to create it but you can use any other IDE or CLI as well. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F924mbrlxr4vytt62dgwq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F924mbrlxr4vytt62dgwq.png" alt="NewJavaProject" width="800" height="713"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After the project is successfully, created we need to add the following dependencies into our pom.xml.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!-- Logging related dependencies starts --&amp;gt;
        &amp;lt;dependency&amp;gt;
            &amp;lt;groupId&amp;gt;org.apache.logging.log4j&amp;lt;/groupId&amp;gt;
            &amp;lt;artifactId&amp;gt;log4j-api&amp;lt;/artifactId&amp;gt;
            &amp;lt;version&amp;gt;${log4j2.version}&amp;lt;/version&amp;gt;
        &amp;lt;/dependency&amp;gt;
        &amp;lt;dependency&amp;gt;
            &amp;lt;groupId&amp;gt;org.apache.logging.log4j&amp;lt;/groupId&amp;gt;
            &amp;lt;artifactId&amp;gt;log4j-core&amp;lt;/artifactId&amp;gt;
            &amp;lt;version&amp;gt;${log4j2.version}&amp;lt;/version&amp;gt;
        &amp;lt;/dependency&amp;gt;
        &amp;lt;dependency&amp;gt;
            &amp;lt;groupId&amp;gt;org.apache.logging.log4j&amp;lt;/groupId&amp;gt;
            &amp;lt;artifactId&amp;gt;log4j-slf4j-impl&amp;lt;/artifactId&amp;gt;
            &amp;lt;version&amp;gt;${log4j2.version}&amp;lt;/version&amp;gt;
        &amp;lt;/dependency&amp;gt;
        &amp;lt;!-- Logging related dependencies ends --&amp;gt;
        &amp;lt;dependency&amp;gt;
            &amp;lt;groupId&amp;gt;org.projectlombok&amp;lt;/groupId&amp;gt;
            &amp;lt;artifactId&amp;gt;lombok&amp;lt;/artifactId&amp;gt;
            &amp;lt;scope&amp;gt;provided&amp;lt;/scope&amp;gt;
            &amp;lt;version&amp;gt;1.18.30&amp;lt;/version&amp;gt;
        &amp;lt;/dependency&amp;gt;
        &amp;lt;!-- Apache Kafka Connect dependencies starts --&amp;gt;
        &amp;lt;dependency&amp;gt;
            &amp;lt;groupId&amp;gt;org.apache.kafka&amp;lt;/groupId&amp;gt;
            &amp;lt;artifactId&amp;gt;connect-runtime&amp;lt;/artifactId&amp;gt;
            &amp;lt;version&amp;gt;3.7.2&amp;lt;/version&amp;gt;
        &amp;lt;/dependency&amp;gt;
        &amp;lt;!-- Apache Kafka Connect dependencies ends --&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The dependencies "&lt;strong&gt;log4j-api&lt;/strong&gt;", "&lt;strong&gt;log4j-core&lt;/strong&gt;" and "&lt;strong&gt;log4j-slf4j-impl&lt;/strong&gt;" are used for logging purposes. We also need to add log4j2.xml to our classpath in order to specify the format of our logs.&lt;/p&gt;

&lt;p&gt;One more thing to note here is "connect-runtime" dependency. We only need to add only this dependency and it brings libraries like "&lt;strong&gt;connect-api&lt;/strong&gt;", "&lt;strong&gt;connect-json&lt;/strong&gt;" &amp;amp; "&lt;strong&gt;connect-transforms&lt;/strong&gt;" transitively. Since, all these libraries are also part of our classpath, our Kafka Connect Application will work as a server and we should be able to interact with it using the REST endpoints. &lt;/p&gt;

&lt;p&gt;We also need to add the &lt;strong&gt;"spring-boot-maven-plugin"&lt;/strong&gt; to our pom.xml under build section. This will allow us to generate the fat jar and provide the class which has the main method. This method is called on the startup of the jar.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;plugins&amp;gt;
    &amp;lt;plugin&amp;gt;
        &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
        &amp;lt;artifactId&amp;gt;spring-boot-maven-plugin&amp;lt;/artifactId&amp;gt;
        &amp;lt;executions&amp;gt;
            &amp;lt;execution&amp;gt;
                &amp;lt;configuration&amp;gt;
                    &amp;lt;mainClass&amp;gt;dev.ankis.KafkaConnectApplication&amp;lt;/mainClass&amp;gt;
                &amp;lt;/configuration&amp;gt;
                &amp;lt;goals&amp;gt;
                    &amp;lt;goal&amp;gt;repackage&amp;lt;/goal&amp;gt;
                &amp;lt;/goals&amp;gt;
            &amp;lt;/execution&amp;gt;
        &amp;lt;/executions&amp;gt;
        &amp;lt;version&amp;gt;3.4.2&amp;lt;/version&amp;gt;
    &amp;lt;/plugin&amp;gt;
&amp;lt;/plugins&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As a last step, we need to add a configuration file with the name "distributed-config.properties" under the src/main/resources.This will store all the configuration properties.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bootstrap.servers=localhost:9092
group.id=connect-cluster
key.converter=org.apache.kafka.connect.json.JsonConverter
value.converter=org.apache.kafka.connect.json.JsonConverter
key.converter.schemas.enable=true
value.converter.schemas.enable=true
offset.storage.topic=connect-offsets
offset.storage.replication.factor=1
status.storage.topic=connect-status
status.storage.replication.factor=1
offset.flush.interval.ms=10000
listeners=HTTP://:8083
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Post these configurations, we can go ahead and run the application.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs517meffz0vxle1i0udm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs517meffz0vxle1i0udm.png" alt="KafkaConnectorLogs" width="800" height="156"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To see if our kafka connector server is up and running, we can run the following cURL&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl --request GET \
  --url http://localhost:8083/connector-plugins
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm7hweard5hwk3qk02knf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm7hweard5hwk3qk02knf.png" alt="Image description" width="800" height="139"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.confluent.io/platform/7.5/connect/references/restapi.html" rel="noopener noreferrer"&gt;Kafka Connect Rest Api&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/apache/kafka/blob/trunk/connect/runtime/src/main/java/org/apache/kafka/connect/cli/ConnectDistributed.java" rel="noopener noreferrer"&gt;Apache Kafka Connect Main Method&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ankit-sood/kafka-connect-standalone" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>kafka</category>
      <category>connect</category>
      <category>eventdriven</category>
      <category>pipeline</category>
    </item>
  </channel>
</rss>
