<?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>Forem: Aayush Ghimire</title>
    <description>The latest articles on Forem by Aayush Ghimire (@aayushghimirey).</description>
    <link>https://forem.com/aayushghimirey</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%2F3731190%2F7e08489f-ebda-418a-b40e-cd17edfd37ea.jpeg</url>
      <title>Forem: Aayush Ghimire</title>
      <link>https://forem.com/aayushghimirey</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/aayushghimirey"/>
    <language>en</language>
    <item>
      <title>#6 Things Every Backend Engineer Should Know (That AI Won't Tell You)</title>
      <dc:creator>Aayush Ghimire</dc:creator>
      <pubDate>Fri, 03 Apr 2026 09:14:17 +0000</pubDate>
      <link>https://forem.com/aayushghimirey/6-things-every-backend-engineer-should-know-that-ai-wont-tell-you-41hg</link>
      <guid>https://forem.com/aayushghimirey/6-things-every-backend-engineer-should-know-that-ai-wont-tell-you-41hg</guid>
      <description>&lt;p&gt;AI can write code. Honestly, it can write it faster and with fewer syntax errors than most of us. But here's what it &lt;em&gt;can't&lt;/em&gt; do: it doesn't know your system's traffic patterns, your database's growth trajectory, your team's ops maturity, or why that one service falls over every Tuesday at 2 AM.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architecture is still yours to own.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This post is aimed at backend engineers — especially those in the Java/Spring Boot world — who want to go beyond CRUD and understand what actually makes systems hold up under pressure.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. 🧵 Thread Pool Sizing — Bigger Is Not Better
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;"more threads = more throughput.? "&lt;/em&gt; It doesn't work that way.&lt;/p&gt;

&lt;h3&gt;
  
  
  CPU-bound vs. IO-bound — why it matters
&lt;/h3&gt;

&lt;p&gt;Your app's tasks fall into two categories:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CPU-bound&lt;/strong&gt; — number crunching, encoding, compression. The thread needs the CPU the whole time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IO-bound&lt;/strong&gt; — database calls, HTTP requests, file reads. The thread spends most of its time &lt;em&gt;waiting&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your app is CPU-bound and you run 200 threads on a 6-core machine, each core is constantly switching between ~33 threads. That context-switching overhead &lt;em&gt;adds&lt;/em&gt; latency — it doesn't reduce it.&lt;/p&gt;

&lt;p&gt;In Spring Boot with an embedded Tomcat server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# application.yml&lt;/span&gt;
&lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;tomcat&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;threads&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt;          &lt;span class="c1"&gt;# default — fine for IO-heavy apps&lt;/span&gt;
      &lt;span class="na"&gt;min-spare&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
    &lt;span class="na"&gt;accept-count&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;   &lt;span class="c1"&gt;# queue size when all threads are busy&lt;/span&gt;
    &lt;span class="na"&gt;max-connections&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8192&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Don't just bump &lt;code&gt;max&lt;/code&gt; to 500.&lt;/strong&gt; Profile your app first. Use tools like JVisualVM or async-profiler to see whether your threads are actually running or just waiting.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  2. 🏊 DB Connection Pool — HikariCP Done Right
&lt;/h2&gt;

&lt;p&gt;Spring Boot ships with HikariCP as the default connection pool, and it's excellent. But "excellent" doesn't mean "correctly configured for your app." The defaults are conservative placeholders — not production settings.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why connection pools matter
&lt;/h3&gt;

&lt;p&gt;Every DB connection is expensive: it holds memory on both the app and DB side, and opening one takes time. A pool keeps a set of connections warm and ready. Too few → threads queue up waiting for a connection. Too many → you overwhelm the database.&lt;/p&gt;

&lt;h3&gt;
  
  
  The HikariCP sizing formula
&lt;/h3&gt;

&lt;p&gt;This comes directly from the HikariCP maintainer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;pool_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;core_count&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;effective_spindle_count&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a 4-core app server talking to a standard SSD-backed Postgres instance:&lt;br&gt;
&lt;code&gt;pool_size = (4 * 2) + 1 = 9&lt;/code&gt; — round up to 10.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# application.yml&lt;/span&gt;
&lt;span class="na"&gt;spring&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;datasource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jdbc:postgresql://localhost:5432/mydb&lt;/span&gt;
    &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${DB_USER}&lt;/span&gt;
    &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${DB_PASS}&lt;/span&gt;
    &lt;span class="na"&gt;hikari&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;maximum-pool-size&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;        &lt;span class="c1"&gt;# max active connections&lt;/span&gt;
      &lt;span class="na"&gt;minimum-idle&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;              &lt;span class="c1"&gt;# keep 5 warm at all times&lt;/span&gt;
      &lt;span class="na"&gt;idle-timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;600000&lt;/span&gt;         &lt;span class="c1"&gt;# remove idle connections after 10 min&lt;/span&gt;
      &lt;span class="na"&gt;connection-timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30000&lt;/span&gt;    &lt;span class="c1"&gt;# fail fast if no connection in 30s&lt;/span&gt;
      &lt;span class="na"&gt;max-lifetime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1800000&lt;/span&gt;        &lt;span class="c1"&gt;# recycle connections every 30 min&lt;/span&gt;
      &lt;span class="na"&gt;pool-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MyAppHikariPool&lt;/span&gt;
      &lt;span class="na"&gt;auto-commit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;           &lt;span class="c1"&gt;# let Spring manage transactions&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The &lt;code&gt;@Transactional&lt;/code&gt; trap
&lt;/h3&gt;

&lt;p&gt;Every &lt;code&gt;@Transactional&lt;/code&gt; method holds a connection for its entire duration. This is the most common way connection pools get exhausted.&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;// ❌ BAD — holds a connection for the entire slow external call&lt;/span&gt;
&lt;span class="nd"&gt;@Transactional&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;OrderResult&lt;/span&gt; &lt;span class="nf"&gt;processOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OrderRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&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="n"&gt;orderRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

    &lt;span class="c1"&gt;// This HTTP call could take 2 seconds&lt;/span&gt;
    &lt;span class="c1"&gt;// Your DB connection is locked the entire time&lt;/span&gt;
    &lt;span class="nc"&gt;PaymentResult&lt;/span&gt; &lt;span class="n"&gt;payment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;paymentGateway&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;charge&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCardToken&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="na"&gt;getTotal&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="na"&gt;setStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isSuccess&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="no"&gt;CONFIRMED&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="no"&gt;FAILED&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;orderRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&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;OrderResult&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="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ GOOD — transaction wraps only the DB work&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;OrderResult&lt;/span&gt; &lt;span class="nf"&gt;processOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OrderRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&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="n"&gt;createOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;              &lt;span class="c1"&gt;// short transaction&lt;/span&gt;
    &lt;span class="nc"&gt;PaymentResult&lt;/span&gt; &lt;span class="n"&gt;payment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;paymentGateway&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;charge&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;  &lt;span class="c1"&gt;// outside transaction&lt;/span&gt;
        &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCardToken&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="na"&gt;getTotal&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="nf"&gt;finalizeOrder&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="na"&gt;getId&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// short transaction&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;private&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt; &lt;span class="nf"&gt;createOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OrderRequest&lt;/span&gt; &lt;span class="n"&gt;request&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;orderRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@Transactional&lt;/span&gt;
&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;OrderResult&lt;/span&gt; &lt;span class="nf"&gt;finalizeOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;PaymentResult&lt;/span&gt; &lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="o"&gt;)&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="n"&gt;orderRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;orElseThrow&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="na"&gt;setStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isSuccess&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="no"&gt;CONFIRMED&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="no"&gt;FAILED&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;OrderResult&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orderRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  3. 🔍 The N+1 Query Problem
&lt;/h2&gt;

&lt;p&gt;If you've ever looked at your logs and seen 100+ queries firing for what should be a single list fetch — you've hit N+1. It's silent, it's common, and it absolutely destroys performance at scale.&lt;/p&gt;

&lt;h3&gt;
  
  
  What causes it
&lt;/h3&gt;

&lt;p&gt;When JPA lazily loads associations, it fires one query to get N parent records, then one query per parent to fetch the child. N parents = N+1 total queries.&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;// Entity setup&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;Order&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@OneToMany&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fetch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FetchType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;LAZY&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// default — lazy&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;OrderItem&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// This looks innocent...&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;Order&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orderRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findAll&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// 1 query&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;Order&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;orders&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;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;order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getItems&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// N queries — one per order&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// If orders has 100 rows: 1 + 100 = 101 queries. Ouch.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Fix 1: &lt;code&gt;@EntityGraph&lt;/code&gt; — declarative and clean
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Repository&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;OrderRepository&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;JpaRepository&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@EntityGraph&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attributePaths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"items"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"items.product"&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;Order&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;findAllWithItems&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

    &lt;span class="nd"&gt;@EntityGraph&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attributePaths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"items"&lt;/span&gt;&lt;span class="o"&gt;})&lt;/span&gt;
    &lt;span class="nc"&gt;Optional&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;findWithItemsById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Fix 2: JOIN FETCH in JPQL — for custom queries
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Query&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SELECT DISTINCT o FROM Order o "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
       &lt;span class="s"&gt;"LEFT JOIN FETCH o.items i "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
       &lt;span class="s"&gt;"LEFT JOIN FETCH i.product "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
       &lt;span class="s"&gt;"WHERE o.status = :status"&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;Order&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;findByStatusWithItems&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@Param&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"status"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;OrderStatus&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Fix 3: Batch fetching (when JOIN FETCH causes cartesian products)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# application.yml&lt;/span&gt;
&lt;span class="na"&gt;spring&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;jpa&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;hibernate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;default_batch_fetch_size&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;  &lt;span class="c1"&gt;# batch N lazy loads into one IN() query&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Enable SQL logging in dev&lt;/strong&gt; to catch N+1 early — before it hits production:&lt;/p&gt;


&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;spring&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;jpa&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;show-sql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;hibernate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;format_sql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;logging&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;org.hibernate.SQL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DEBUG&lt;/span&gt;
    &lt;span class="na"&gt;org.hibernate.type.descriptor.sql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TRACE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  4. 📦 Response Payload Size — Serialize Less, Go Faster
&lt;/h2&gt;

&lt;p&gt;Serialization and deserialization are not free. Every null field you send gets serialized by the server, transmitted over the wire, and deserialized on the client — for nothing.&lt;/p&gt;

&lt;p&gt;This compounds fast: 1000 requests/second × 20 unnecessary null fields = measurable CPU and bandwidth overhead.&lt;/p&gt;

&lt;h3&gt;
  
  
  Exclude nulls globally
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Apply to your DTO base class or globally&lt;/span&gt;
&lt;span class="nd"&gt;@JsonInclude&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JsonInclude&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Include&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;NON_NULL&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;ApiResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="no"&gt;T&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;errorCode&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;// null in success cases — won't be serialized&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// null in success cases — won't be serialized&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or globally via config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# application.yml&lt;/span&gt;
&lt;span class="na"&gt;spring&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;jackson&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;default-property-inclusion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;non_null&lt;/span&gt;
    &lt;span class="na"&gt;serialization&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;write-dates-as-timestamps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Use DTOs — never return entities directly
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ BAD — sends the whole entity including sensitive fields&lt;/span&gt;
&lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/users/{id}"&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;User&lt;/span&gt; &lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@PathVariable&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;userRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;orElseThrow&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ GOOD — return only what the client needs&lt;/span&gt;
&lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/users/{id}"&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;UserSummaryDTO&lt;/span&gt; &lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@PathVariable&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;userRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;UserSummaryDTO:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;orElseThrow&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ResourceNotFoundException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"User not found"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// The DTO&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;UserSummaryDTO&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="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="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;UserSummaryDTO&lt;/span&gt; &lt;span class="nf"&gt;from&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="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;UserSummaryDTO&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="na"&gt;getId&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="na"&gt;getUsername&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="na"&gt;getEmail&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;
  
  
  Projection queries — skip the entity entirely for reads
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Interface-based projection — JPA only selects these two columns&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;UserSummary&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="nf"&gt;getId&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;getUsername&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;interface&lt;/span&gt; &lt;span class="nc"&gt;UserRepository&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;JpaRepository&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;UserSummary&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;findAllProjectedBy&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  5. ⚡ Event-Driven Architecture with Kafka — Decouple or Die
&lt;/h2&gt;

&lt;p&gt;As your system grows, REST calls between services become a liability. One slow service stalls the caller. One down service causes cascades. Every consumer adds coupling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Kafka flips this model:&lt;/strong&gt; producers don't care who consumes. Consumers don't block the producer.&lt;/p&gt;

&lt;h3&gt;
  
  
  The snapshot pattern — stop making REST calls for validation
&lt;/h3&gt;

&lt;p&gt;Classic problem: &lt;code&gt;Order Service&lt;/code&gt; needs to validate that an inventory item exists before placing an order. Naive solution: REST call to &lt;code&gt;Inventory Service&lt;/code&gt; on every order.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Better:&lt;/strong&gt; Inventory Service publishes events. Order Service consumes them and keeps a local snapshot.&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;// Inventory Service — publish event when inventory is created/updated&lt;/span&gt;
&lt;span class="nd"&gt;@Service&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;InventoryService&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;InventoryEvent&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="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Inventory&lt;/span&gt; &lt;span class="nf"&gt;createInventory&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CreateInventoryRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Inventory&lt;/span&gt; &lt;span class="n"&gt;inventory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;inventoryRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Inventory&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&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;"inventory.events"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getSku&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InventoryEvent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getId&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
                &lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getSku&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
                &lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getQuantity&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
                &lt;span class="nc"&gt;EventType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;CREATED&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;inventory&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Order Service — consume and snapshot&lt;/span&gt;
&lt;span class="nd"&gt;@Service&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;InventorySnapshotConsumer&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;InventorySnapshotRepository&lt;/span&gt; &lt;span class="n"&gt;snapshotRepo&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@KafkaListener&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;topics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"inventory.events"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;groupId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"order-service"&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;onInventoryEvent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;InventoryEvent&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="nc"&gt;InventorySnapshot&lt;/span&gt; &lt;span class="n"&gt;snapshot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;snapshotRepo&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findBySku&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;getSku&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;orElse&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;InventorySnapshot&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

        &lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setSku&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;getSku&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setAvailableQuantity&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;getQuantity&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setLastUpdated&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Instant&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;now&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="n"&gt;snapshotRepo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Now validation is just a local DB lookup — no REST call&lt;/span&gt;
&lt;span class="nd"&gt;@Service&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;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;validateInventory&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;sku&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;InventorySnapshot&lt;/span&gt; &lt;span class="n"&gt;snapshot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;snapshotRepo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findBySku&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sku&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;orElseThrow&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;InventoryNotFoundException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sku&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;snapshot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getAvailableQuantity&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&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="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InsufficientInventoryException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sku&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;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Trade-off:&lt;/strong&gt; Events give you eventual consistency. If the snapshot hasn't caught up yet, validation may pass on stale data. Design your system to handle this — use idempotent consumers, compensating transactions, or re-validation at fulfillment time.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  6. 🏗️ Know How to Architect — AI Doesn't Know Your Requirements
&lt;/h2&gt;

&lt;p&gt;This one isn't a config snippet. It's the meta-skill.&lt;/p&gt;

&lt;p&gt;AI can generate a microservice scaffold in seconds. It cannot answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;"Should this be a separate service or stay in the monolith?"&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"Will this DB schema survive 10x traffic?"&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"Is this the right consistency model for this transaction?"&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These require &lt;strong&gt;you&lt;/strong&gt; to understand the system you're building.&lt;/p&gt;

&lt;h3&gt;
  
  
  The questions you should be asking
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Before designing a service:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What's the expected read/write ratio?&lt;/li&gt;
&lt;li&gt;What's the consistency requirement? Can we tolerate stale data for 500ms?&lt;/li&gt;
&lt;li&gt;What fails if this service goes down? Who's the caller? Can they retry?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Before adding a database:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is this data relational or document-shaped?&lt;/li&gt;
&lt;li&gt;What are the query patterns? Point lookups, range scans, aggregations?&lt;/li&gt;
&lt;li&gt;Do we need ACID guarantees, or is BASE acceptable?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Before scaling horizontally:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What's the actual bottleneck? CPU? DB? Network? Memory?&lt;/li&gt;
&lt;li&gt;Is state being stored in-process? (That's a problem when you add instances.)&lt;/li&gt;
&lt;li&gt;Can we cache this instead of scaling?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Use AI right
&lt;/h3&gt;

&lt;p&gt;AI is a force multiplier when you're in the driver's seat:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Good use of AI&lt;/span&gt;
"Here's my schema and access patterns. 
 Write me a JPA repository with an EntityGraph for this use case."

&lt;span class="gh"&gt;# Bad use of AI&lt;/span&gt;
"Build me a scalable microservice for inventory management."
&lt;span class="gh"&gt;# You'll get something that looks right but may be completely wrong for your needs.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The engineers who thrive in the AI era aren't the best prompt writers. They're the ones who can &lt;strong&gt;review, challenge, and take ownership&lt;/strong&gt; of what AI produces.&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Concept&lt;/th&gt;
&lt;th&gt;Key Takeaway&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Thread Pool&lt;/td&gt;
&lt;td&gt;Size to your workload type — CPU-bound ≠ IO-bound&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;HikariCP&lt;/td&gt;
&lt;td&gt;Configure pool size explicitly; keep transactions short&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;N+1 Queries&lt;/td&gt;
&lt;td&gt;Use &lt;code&gt;@EntityGraph&lt;/code&gt; or JOIN FETCH; log SQL in dev&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Payload Size&lt;/td&gt;
&lt;td&gt;Exclude nulls; use DTOs and projections&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Kafka Events&lt;/td&gt;
&lt;td&gt;Snapshot remote data locally; remove synchronous coupling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Architecture&lt;/td&gt;
&lt;td&gt;Own your design decisions — AI doesn't know your constraints&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;p&gt;&lt;em&gt;If you're working with Java and Spring Boot and want to go deeper on any of these topics, drop a comment. Happy to do a follow-up on any specific area — Kafka exactly-once semantics, HikariCP tuning for read replicas, or virtual threads in Java 21 are all on the list.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;#java #springboot #backend #softwaredevelopment #architecture&lt;/em&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>springboot</category>
      <category>backend</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Why PostgreSQL RLS Is Hard in Spring Boot and How I Simplified It</title>
      <dc:creator>Aayush Ghimire</dc:creator>
      <pubDate>Tue, 27 Jan 2026 10:30:15 +0000</pubDate>
      <link>https://forem.com/aayushghimirey/why-postgresql-rls-is-hard-in-spring-boot-and-how-i-simplified-it-4124</link>
      <guid>https://forem.com/aayushghimirey/why-postgresql-rls-is-hard-in-spring-boot-and-how-i-simplified-it-4124</guid>
      <description>&lt;h2&gt;
  
  
  Startup-time validation in action
&lt;/h2&gt;

&lt;p&gt;When PostgreSQL RLS configuration is missing or incorrect, the application &lt;strong&gt;fails at startup&lt;/strong&gt; instead of failing later at runtime.&lt;/p&gt;

&lt;p&gt;This prevents silent security assumptions and production-time surprises.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: RLS policy validation failure
&lt;/h3&gt;

&lt;p&gt;Below is an example of what happens when a required RLS policy is missing or misconfigured:&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%2Fnw8t5aaoch8gx2g71ku9.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%2Fnw8t5aaoch8gx2g71ku9.png" alt="Application fails during startup due to missing RLS policy" width="800" height="281"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At startup, the library validates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Whether the table exists&lt;/li&gt;
&lt;li&gt;Whether RLS is enabled on the table&lt;/li&gt;
&lt;li&gt;Whether the required policy exists&lt;/li&gt;
&lt;li&gt;Whether required session variables are configured&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If any of these checks fail, the application &lt;strong&gt;does not start&lt;/strong&gt; and provides a clear failure message.&lt;/p&gt;




&lt;h2&gt;
  
  
  Before we dive in, let’s be clear
&lt;/h2&gt;

&lt;p&gt;This library &lt;strong&gt;does not manage database DDL&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It does &lt;strong&gt;not&lt;/strong&gt; create tables, policies, or RLS rules
&lt;/li&gt;
&lt;li&gt;It does &lt;strong&gt;not&lt;/strong&gt; require superuser or elevated database roles
&lt;/li&gt;
&lt;li&gt;It only &lt;strong&gt;validates&lt;/strong&gt; that the database security setup matches what the application expects
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;RLS policies and table configuration should be managed using standard database migration tools such as &lt;strong&gt;Flyway&lt;/strong&gt; or &lt;strong&gt;Liquibase&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
This is the &lt;strong&gt;recommended and preferred approach&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How startup validation works
&lt;/h2&gt;

&lt;p&gt;RLS expectations are defined using annotations in JPA entities and configuration.&lt;/p&gt;

&lt;p&gt;At application startup:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The configuration is read&lt;/li&gt;
&lt;li&gt;The database state is inspected&lt;/li&gt;
&lt;li&gt;Validation errors are reported immediately&lt;/li&gt;
&lt;li&gt;If everything is valid, the application starts successfully&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This ensures RLS issues are caught &lt;strong&gt;early&lt;/strong&gt;, not under production load.&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%2Fhefrowmn8939ywi5776u.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%2Fhefrowmn8939ywi5776u.png" alt="Startup validation flow" width="691" height="157"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Binding the RLS session context
&lt;/h2&gt;

&lt;p&gt;Session variables required by RLS policies are bound &lt;strong&gt;per transaction&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;There are &lt;strong&gt;two supported ways&lt;/strong&gt; to bind the session context:&lt;/p&gt;

&lt;h3&gt;
  
  
  1️⃣ Annotation-based binding (recommended)
&lt;/h3&gt;

&lt;p&gt;Bind session variables directly on &lt;code&gt;@Transactional&lt;/code&gt; method parameters.&lt;/p&gt;

&lt;h3&gt;
  
  
  2️⃣ Programmatic binding
&lt;/h3&gt;

&lt;p&gt;Bind session variables manually using a provided context API.&lt;/p&gt;

&lt;p&gt;Both approaches use &lt;strong&gt;transaction-scoped &lt;code&gt;set_config(..., true)&lt;/code&gt;&lt;/strong&gt;, ensuring variables are automatically cleared after commit or rollback.&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%2F5vih24zbsqyt3a1yk15i.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%2F5vih24zbsqyt3a1yk15i.png" alt="RLS session binding options" width="717" height="170"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;The project is available on &lt;strong&gt;Maven Central&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;io.github.aayushghimirey:jpa-postgres-rls:2.0.1&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;🔗 Maven Central&lt;br&gt;&lt;br&gt;
&lt;a href="https://mvnrepository.com/artifact/io.github.aayushghimirey/jpa-postgres-rls/2.0.1" rel="noopener noreferrer"&gt;https://mvnrepository.com/artifact/io.github.aayushghimirey/jpa-postgres-rls/2.0.1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🔗 GitHub repository&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/aayushghimirey/jpa-postgres-rls" rel="noopener noreferrer"&gt;https://github.com/aayushghimirey/jpa-postgres-rls&lt;/a&gt;&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>springboot</category>
      <category>opensource</category>
      <category>jpa</category>
    </item>
    <item>
      <title>Simplifying PostgreSQL RLS in JPA with JPA Postgres RLS</title>
      <dc:creator>Aayush Ghimire</dc:creator>
      <pubDate>Sun, 25 Jan 2026 10:47:20 +0000</pubDate>
      <link>https://forem.com/aayushghimirey/simplifying-postgresql-rls-in-jpa-with-jpa-postgres-rls-1607</link>
      <guid>https://forem.com/aayushghimirey/simplifying-postgresql-rls-in-jpa-with-jpa-postgres-rls-1607</guid>
      <description>&lt;p&gt;Bringing PostgreSQL Row-Level Security to JPA&lt;br&gt;
I’ve been working on a lightweight Spring Boot library to simplify data isolation. JPA Postgres RLS automates PostgreSQL Row-Level Security (RLS) binding for JPA entities.&lt;br&gt;
The challenge: keeping the application context and the database session in sync.&lt;br&gt;
JPA Postgres RLS solves this by:&lt;br&gt;
Validating RLS rules at application startup&lt;br&gt;
Using Spring AOP to bind session variables via custom annotations&lt;br&gt;
Ensuring type-safe mapping from Java objects to SQL session settings&lt;br&gt;
Key features:&lt;br&gt;
Automatic @RlsSession binding&lt;br&gt;
Fail-fast startup validation for missing policies or variables&lt;br&gt;
Transaction-scoped session variables&lt;br&gt;
Built with Java 21 and Spring Boot 3.4, this library is ideal for multi-tenant SaaS applications. I’d love your feedback and contributions!&lt;/p&gt;

&lt;p&gt;Why I made this:&lt;br&gt;
While working with PostgreSQL RLS, I often forgot to create policies or enable row-level security. Simple mistakes like these or mismatched session variable names, could break an app at runtime. There was no way to catch these issues automatically, and no one would notice until something suddenly failed.&lt;/p&gt;

&lt;p&gt;So, I decided to build a core library with startup validation and automatic session binding. The project grew as I added more ideas, and I made it open-source so others facing the same challenges can benefit.&lt;/p&gt;

&lt;p&gt;Lessons from version 1:&lt;/p&gt;

&lt;p&gt;Initially, I tried automatic policy creation and RLS enabling, but working with DDL proved error-prone. For version 2, I focused on validation and auto-filling session variables instead.&lt;/p&gt;

&lt;p&gt;Check out the code: &lt;a href="https://github.com/aayushghimirey/jpa-postgres-rls.git" rel="noopener noreferrer"&gt;https://github.com/aayushghimirey/jpa-postgres-rls.git&lt;/a&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>postgres</category>
      <category>showdev</category>
      <category>springboot</category>
    </item>
  </channel>
</rss>
