<?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: Shubham Bhati</title>
    <description>The latest articles on DEV Community by Shubham Bhati (@shubham_bhati).</description>
    <link>https://dev.to/shubham_bhati</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3935276%2Fa996911a-6c8f-4d5e-ab58-f46b8c58c670.png</url>
      <title>DEV Community: Shubham Bhati</title>
      <link>https://dev.to/shubham_bhati</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shubham_bhati"/>
    <language>en</language>
    <item>
      <title>Beyond the Cache Miss: Designing Resilient Caching Layers with Redis Degradation Strategies</title>
      <dc:creator>Shubham Bhati</dc:creator>
      <pubDate>Mon, 22 Jun 2026 10:22:02 +0000</pubDate>
      <link>https://dev.to/shubham_bhati/beyond-the-cache-miss-designing-resilient-caching-layers-with-redis-degradation-strategies-e7k</link>
      <guid>https://dev.to/shubham_bhati/beyond-the-cache-miss-designing-resilient-caching-layers-with-redis-degradation-strategies-e7k</guid>
      <description>&lt;h2&gt;
  
  
  1. The Anatomy of a Cache Disaster
&lt;/h2&gt;

&lt;p&gt;To design a solution, we must first analyze how cache failures manifest as systemic outages. Consider a standard read-through caching pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Client] ---&amp;gt; [API Gateway] ---&amp;gt; [Product Service] 
                                    |         |
                              (1) Read    (2) Miss? Read DB
                                    v         v
                                 [Redis]   [PostgreSQL]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Failure Cascade
&lt;/h3&gt;

&lt;p&gt;If Redis latency increases from 2ms to 2000ms (due to network congestion or CPU saturation), the following cascade occurs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Thread Pool Exhaustion:&lt;/strong&gt; The API container's HTTP worker threads (e.g., Tomcat, Netty) wait on Redis read timeouts. New incoming requests queue up, quickly exhausting the container's thread pool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Cache Stampede (Thundering Herd):&lt;/strong&gt; If the Redis connection drops entirely, &lt;em&gt;all&lt;/em&gt; concurrent requests for a popular resource miss simultaneously. They bypass the cache and hit the downstream database together.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database Demolition:&lt;/strong&gt; The database, designed for a fraction of the cache's read volume, experiences immediate connection pool saturation, CPU spikes to 100%, and starts dropping requests. The entire platform goes offline.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Root Cause Analysis (RCA)
&lt;/h3&gt;

&lt;p&gt;The root cause is &lt;strong&gt;tight coupling and synchronous blocking&lt;/strong&gt; on the caching layer. The application treats Redis as a &lt;em&gt;hard dependency&lt;/em&gt; rather than an &lt;em&gt;opportunistic optimization&lt;/em&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. The Resilient Architecture: Multi-Tier Caching &amp;amp; Circuit Breaking
&lt;/h2&gt;

&lt;p&gt;To build a resilient caching layer, we must implement three core design patterns:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Dual-Layer Caching (L1/L2):&lt;/strong&gt; 

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;L1 (Local Memory):&lt;/strong&gt; A small, fast, in-memory cache (e.g., Caffeine) residing within the application process JVM.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;L2 (Distributed Cache):&lt;/strong&gt; Redis.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Circuit Breaking &amp;amp; Fallbacks:&lt;/strong&gt; Wrapping Redis interactions inside a circuit breaker. If Redis error rates or response times cross a threshold, the breaker trips, bypassing Redis entirely and falling back to L1 or a safe database read-through.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Asynchronous Non-Blocking Refresh (Stale-While-Revalidate):&lt;/strong&gt; Serving slightly stale data from L1/L2 while asynchronously updating the cache in the background.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                  +---------------------------------------+
                  |           Product Service             |
                  +---------------------------------------+
                                      |
                           [Check L1 Cache (Local)]
                                      | (Miss)
                                      v
                        +----------------------------+
                        |   Resilience4j Breaker     |
                        +----------------------------+
                          /                        \
                 (Closed)/                          \(Open / Half-Open)
                        v                            v
               [Check L2 (Redis)]             [Fallback Path]
                 /            \                      |
         (Hit)  /      (Miss)  \ (Error)             |
               v                v                    v
         [Return Data]   [Query DB &amp;amp; Populate]  [Query DB (Rate Limited) / Stale Data]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  3. Implementation: Building a Resilient Cache Manager in Spring Boot
&lt;/h2&gt;

&lt;p&gt;Let's implement this architecture using &lt;strong&gt;Java 17&lt;/strong&gt;, &lt;strong&gt;Spring Boot 3&lt;/strong&gt;, &lt;strong&gt;Caffeine (L1)&lt;/strong&gt;, &lt;strong&gt;Redis (L2)&lt;/strong&gt;, and &lt;strong&gt;Resilience4j&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dependency Configuration (&lt;code&gt;pom.xml&lt;/code&gt;)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependencies&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- Spring Boot Starter Cache --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework.boot&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-boot-starter-cache&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- Redis --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework.boot&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-boot-starter-data-redis&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- Caffeine (L1 Cache) --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;com.github.ben-manes.caffeine&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;caffeine&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- Resilience4j Circuit Breaker --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;io.github.resilience4j&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;resilience4j-spring-boot3&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;2.1.0&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependencies&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Resilient Cache Layer Implementation
&lt;/h3&gt;

&lt;p&gt;We will write a custom &lt;code&gt;ResilientProductService&lt;/code&gt; that coordinates the L1 cache, the L2 Redis cache protected by a circuit breaker, and the database fallback.&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;package&lt;/span&gt; &lt;span class="nn"&gt;com.example.cache.service&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.example.cache.model.Product&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.example.cache.repository.ProductRepository&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.github.benmanes.caffeine.cache.Cache&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;lombok.RequiredArgsConstructor&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;lombok.extern.slf4j.Slf4j&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.data.redis.core.RedisTemplate&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.stereotype.Service&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.time.Duration&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.Optional&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@Slf4j&lt;/span&gt;
&lt;span class="nd"&gt;@Service&lt;/span&gt;
&lt;span class="nd"&gt;@RequiredArgsConstructor&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;ResilientProductService&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;ProductRepository&lt;/span&gt; &lt;span class="n"&gt;productRepository&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;RedisTemplate&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;Product&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;redisTemplate&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;Cache&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;Product&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;l1CaffeineCache&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Local JVM cache&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="no"&gt;REDIS_KEY_PREFIX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"product:"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="no"&gt;REDIS_CIRCUIT_BREAKER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"redisService"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Fetch Product with a resilient multi-tier fallback architecture.
     * 1. Try L1 Cache (Caffeine)
     * 2. Try L2 Cache (Redis) - Wrapped in Circuit Breaker
     * 3. Database Fallback (with automatic L1/L2 repopulation)
     */&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt; &lt;span class="nf"&gt;getProduct&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;productId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Step 1: Query L1 (In-Memory JVM Cache) - Instant, cannot fail due to network&lt;/span&gt;
        &lt;span class="nc"&gt;Product&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;l1CaffeineCache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getIfPresent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;productId&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;product&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"L1 Cache Hit for product: {}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;productId&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;product&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Step 2: Query L2 (Redis) via Circuit Breaker&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;getProductFromL2WithCircuitBreaker&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;productId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Redis lookup protected by Resilience4j.
     * If Redis is slow or down, the fallbackMethod is executed.
     */&lt;/span&gt;
    &lt;span class="nd"&gt;@CircuitBreaker&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="no"&gt;REDIS_CIRCUIT_BREAKER&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fallbackMethod&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"fallbackGetProductFromDb"&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;Product&lt;/span&gt; &lt;span class="nf"&gt;getProductFromL2WithCircuitBreaker&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;productId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"L1 Cache Miss. Querying L2 (Redis) for product: {}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;productId&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;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;REDIS_KEY_PREFIX&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;productId&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// This operation will throw an exception if Redis is unreachable, &lt;/span&gt;
        &lt;span class="c1"&gt;// triggering the circuit breaker and fallback.&lt;/span&gt;
        &lt;span class="nc"&gt;Product&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;redisTemplate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;opsForValue&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&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;product&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"L2 Cache Hit for product: {}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;productId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="c1"&gt;// Populate L1 cache so subsequent reads avoid L2/Network completely&lt;/span&gt;
            &lt;span class="n"&gt;l1CaffeineCache&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="n"&gt;productId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;product&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;product&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Step 3: L2 Miss -&amp;gt; Query Database&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;warn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"L2 Cache Miss for product: {}. Fetching from Database."&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;productId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;Product&lt;/span&gt; &lt;span class="n"&gt;dbProduct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;productRepository&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;productId&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;"Product not found: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;productId&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

        &lt;span class="c1"&gt;// Asynchronously or synchronously populate caches&lt;/span&gt;
        &lt;span class="n"&gt;populateCaches&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;productId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dbProduct&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;dbProduct&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Fallback Method executed when the Redis Circuit Breaker is OPEN or Redis throws an Exception.
     * This bypasses Redis to protect database connection pools from starvation.
     */&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt; &lt;span class="nf"&gt;fallbackGetProductFromDb&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;productId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Throwable&lt;/span&gt; &lt;span class="n"&gt;throwable&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Redis Cache Unavailable (Circuit Breaker status/error: {}). Falling back directly to DB."&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; 
                  &lt;span class="n"&gt;throwable&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getMessage&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

        &lt;span class="c1"&gt;// Under degradation, we fetch from the database. &lt;/span&gt;
        &lt;span class="c1"&gt;// Optional: Implement a rate-limiter or semaphore here to prevent database overload!&lt;/span&gt;
        &lt;span class="nc"&gt;Product&lt;/span&gt; &lt;span class="n"&gt;dbProduct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;productRepository&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;productId&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;"Product not found: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;productId&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

        &lt;span class="c1"&gt;// Populate L1 (Local Memory) only. Do NOT touch Redis while it is struggling.&lt;/span&gt;
        &lt;span class="n"&gt;l1CaffeineCache&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="n"&gt;productId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dbProduct&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;dbProduct&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;populateCaches&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;productId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Populate L1&lt;/span&gt;
        &lt;span class="n"&gt;l1CaffeineCache&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="n"&gt;productId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Populate L2 (Redis) with write-timeout protection&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;redisTemplate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;opsForValue&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="no"&gt;REDIS_KEY_PREFIX&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;productId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; 
                &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; 
                &lt;span class="nc"&gt;Duration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofMinutes&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="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;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to populate L2 Redis Cache. Suppressing exception to avoid client disruption."&lt;/span&gt;&lt;span class="o"&gt;,&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;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Application Configuration (&lt;code&gt;application.yml&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;The circuit breaker config is critical. We must set low timeouts for Redis connections and configure the circuit breaker sensitivity to trip quickly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;spring&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;localhost&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;6379&lt;/span&gt;
      &lt;span class="na"&gt;connect-timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;200ms&lt;/span&gt; &lt;span class="c1"&gt;# Short connect timeout&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;100ms&lt;/span&gt;         &lt;span class="c1"&gt;# Very aggressive read timeout for microsecond cache lookups&lt;/span&gt;

&lt;span class="na"&gt;resilience4j&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;circuitbreaker&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;instances&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;redisService&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;slidingWindowType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;COUNT_BASED&lt;/span&gt;
        &lt;span class="na"&gt;slidingWindowSize&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;           &lt;span class="c1"&gt;# Track last 20 requests&lt;/span&gt;
        &lt;span class="na"&gt;minimumNumberOfCalls&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;# Min calls before calculating error rate&lt;/span&gt;
        &lt;span class="na"&gt;failureRateThreshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50&lt;/span&gt;         &lt;span class="c1"&gt;# Trip if 50% of last 20 calls failed&lt;/span&gt;
        &lt;span class="na"&gt;slowCallRateThreshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;75&lt;/span&gt;        &lt;span class="c1"&gt;# Trip if 75% of calls are slower than limit&lt;/span&gt;
        &lt;span class="na"&gt;slowCallDurationThreshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;50ms&lt;/span&gt;  &lt;span class="c1"&gt;# Call is "slow" if it takes &amp;gt; 50ms&lt;/span&gt;
        &lt;span class="na"&gt;waitDurationInOpenState&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;15s&lt;/span&gt;     &lt;span class="c1"&gt;# Keep breaker open for 15s before retrying&lt;/span&gt;
        &lt;span class="na"&gt;permittedNumberOfCallsInHalfOpenState&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
        &lt;span class="na"&gt;automaticTransitionFromOpenToHalfOpenEnabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  4. Mitigating Cache Stampede: Mutex Locking
&lt;/h2&gt;

&lt;p&gt;If a highly popular cache key expires (e.g., home page configuration), the fallback database lookup can still trigger a spike. To solve this, we implement &lt;strong&gt;Single-Flight Lock&lt;/strong&gt; (using a Mutex lock / local synchronization) to ensure only &lt;em&gt;one&lt;/em&gt; thread fetches the missing data from the database, while other concurrent requests wait or yield stale data.&lt;/p&gt;

&lt;p&gt;Below is an implementation of a thread-safe local lock bypass for database reads:&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.util.concurrent.ConcurrentHashMap&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.concurrent.locks.ReentrantLock&lt;/span&gt;&lt;span class="o"&gt;;&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;StampedeProofProductService&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;ProductRepository&lt;/span&gt; &lt;span class="n"&gt;productRepository&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;Cache&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;Product&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;l1Cache&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;ConcurrentHashMap&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;ReentrantLock&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;keyLocks&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;ConcurrentHashMap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt; &lt;span class="nf"&gt;getProductWithStampedeProtection&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;productId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Product&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;l1Cache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getIfPresent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;productId&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;product&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&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;product&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Get or create a lock specific to this productId&lt;/span&gt;
        &lt;span class="nc"&gt;ReentrantLock&lt;/span&gt; &lt;span class="n"&gt;lock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;keyLocks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;computeIfAbsent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;productId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&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;ReentrantLock&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tryLock&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// Double-checked locking pattern&lt;/span&gt;
                &lt;span class="nc"&gt;Product&lt;/span&gt; &lt;span class="n"&gt;doubleCheck&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;l1Cache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getIfPresent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;productId&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;doubleCheck&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&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;doubleCheck&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
                &lt;span class="o"&gt;}&lt;/span&gt;

                &lt;span class="c1"&gt;// Fetch from Database&lt;/span&gt;
                &lt;span class="nc"&gt;Product&lt;/span&gt; &lt;span class="n"&gt;dbProduct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;productRepository&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;productId&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;l1Cache&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="n"&gt;productId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dbProduct&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;dbProduct&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;lock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;unlock&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
                &lt;span class="n"&gt;keyLocks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;remove&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;productId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Clean up map&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// If lock cannot be acquired, a concurrent thread is already pulling from the DB.&lt;/span&gt;
            &lt;span class="c1"&gt;// Option A: Sleep briefly and retry local cache lookup.&lt;/span&gt;
            &lt;span class="c1"&gt;// Option B: Serve a stale/cached default payload.&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;handleContentionFallback&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;productId&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="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt; &lt;span class="nf"&gt;handleContentionFallback&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;productId&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="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sleep&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Small backoff&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;InterruptedException&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;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;currentThread&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;interrupt&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="c1"&gt;// Retry once from L1&lt;/span&gt;
        &lt;span class="nc"&gt;Product&lt;/span&gt; &lt;span class="n"&gt;fallback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;l1Cache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getIfPresent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;productId&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;fallback&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&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;fallback&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="c1"&gt;// Last-resort mock / read-through fallback logic&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;productId&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Fallback Limited Details"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  5. Operational Verification: Testing Caching Failure Modes
&lt;/h2&gt;

&lt;p&gt;To verify your degradation layer is functioning, run architectural chaos testing:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Test Case&lt;/th&gt;
&lt;th&gt;Simulation Action&lt;/th&gt;
&lt;th&gt;Expected System Behavior&lt;/th&gt;
&lt;th&gt;Verified?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Normal Path&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Warm cache read&lt;/td&gt;
&lt;td&gt;L1 Hit (0ms overhead) / L2 Hit (1-3ms latency).&lt;/td&gt;
&lt;td&gt;[ ]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;L2 Connection Drop&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Block port &lt;code&gt;6379&lt;/code&gt; using &lt;code&gt;iptables&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;First few requests trigger timeouts. Circuit breaker trips to &lt;strong&gt;OPEN&lt;/strong&gt;. Subsequent requests go directly to DB / L1 without querying Redis.&lt;/td&gt;
&lt;td&gt;[ ]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Redis CPU Spike (100%)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Execute complex script in Redis&lt;/td&gt;
&lt;td&gt;Reads exceed &lt;code&gt;50ms&lt;/code&gt; timeout threshold. Circuit Breaker transitions to &lt;strong&gt;OPEN&lt;/strong&gt; due to &lt;code&gt;slowCallRateThreshold&lt;/code&gt;. DB protected.&lt;/td&gt;
&lt;td&gt;[ ]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Self-Healing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Unblock port &lt;code&gt;6379&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Circuit Breaker goes to &lt;strong&gt;HALF-OPEN&lt;/strong&gt; after &lt;code&gt;15s&lt;/code&gt;. Sends 5 test requests. Passes. Breaker goes &lt;strong&gt;CLOSED&lt;/strong&gt;. System restored automatically.&lt;/td&gt;
&lt;td&gt;[ ]&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Summary: Caching Best Practices for Architects
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Establish Aggressive Timeouts:&lt;/strong&gt; Never use default timeouts for cache connections. For Redis, connection timeouts should be &lt;code&gt;&amp;lt;200ms&lt;/code&gt;, and command execution timeouts &lt;code&gt;&amp;lt;100ms&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never Let Redis Failure Crash the App:&lt;/strong&gt; Wrap distributed cache calls in a fallback or a circuit breaker.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep L1 Lean:&lt;/strong&gt; Use L1 (Caffeine/Ehcache) to cache high-frequency read keys with extremely short TTLs (e.g., 30-60 seconds) to guard against sudden hot-key spikes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log &amp;amp; Monitor Cache State Transitions:&lt;/strong&gt; Create alerts for Circuit Breaker status changes (&lt;code&gt;CLOSED&lt;/code&gt; to &lt;code&gt;OPEN&lt;/code&gt;) to detect issues before they affect end-users.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>redis</category>
      <category>systemdesign</category>
      <category>java</category>
      <category>springboot</category>
    </item>
    <item>
      <title>Deep Dive into Java HashMap Internals: Conquering Hash Collisions and Preventing Production Outages</title>
      <dc:creator>Shubham Bhati</dc:creator>
      <pubDate>Mon, 15 Jun 2026 10:43:58 +0000</pubDate>
      <link>https://dev.to/shubham_bhati/deep-dive-into-java-hashmap-internals-conquering-hash-collisions-and-preventing-production-outages-5fb1</link>
      <guid>https://dev.to/shubham_bhati/deep-dive-into-java-hashmap-internals-conquering-hash-collisions-and-preventing-production-outages-5fb1</guid>
      <description>&lt;h2&gt;
  
  
  1. The Core Architecture: Buckets, Nodes, and Memory
&lt;/h2&gt;

&lt;p&gt;At its core, a &lt;code&gt;HashMap&lt;/code&gt; is an array of buckets, dynamically allocated and resized. In the JDK source code, this array is represented as:&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;transient&lt;/span&gt; &lt;span class="nc"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;K&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="no"&gt;V&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;[]&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each bucket is a singly linked list (or a red-black tree, as we'll see later) composed of &lt;code&gt;Node&amp;lt;K,V&amp;gt;&lt;/code&gt; instances. Let's look at the structure of a standard &lt;code&gt;Node&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;K&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="no"&gt;V&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Entry&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;K&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="no"&gt;V&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;final&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;hash&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// Cached hash value of the key&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="no"&gt;K&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="no"&gt;V&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="nc"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;K&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="no"&gt;V&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// Pointer to the next node in the bucket&lt;/span&gt;

    &lt;span class="c1"&gt;// ... constructor and standard methods&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Key Internal Thresholds
&lt;/h3&gt;

&lt;p&gt;To understand how &lt;code&gt;HashMap&lt;/code&gt; maintains its performance, we must look at three critical state variables:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Capacity (&lt;code&gt;capacity&lt;/code&gt;)&lt;/strong&gt;: The number of buckets in the hash table. It is always a power of $2$ (defaulting to $16$ upon lazy initialization).&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Load Factor (&lt;code&gt;loadFactor&lt;/code&gt;)&lt;/strong&gt;: A measure of how full the hash table is allowed to get before its capacity is automatically increased. The default value is &lt;code&gt;0.75f&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Threshold (&lt;code&gt;threshold&lt;/code&gt;)&lt;/strong&gt;: The product of Capacity and Load Factor (&lt;code&gt;capacity * loadFactor&lt;/code&gt;). If the size of the map exceeds this threshold, a &lt;code&gt;resize()&lt;/code&gt; operation is triggered, doubling the bucket array capacity.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  2. The Hashing Pipeline: From Object to Bucket Index
&lt;/h2&gt;

&lt;p&gt;To map an arbitrary key to an array index, Java must translate the key’s 32-bit &lt;code&gt;hashCode()&lt;/code&gt; integer into a valid array index within the range &lt;code&gt;[0, capacity - 1]&lt;/code&gt;. This is done via a two-step process.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+------------------+     Step 1: Bitwise Perturbation     +-------------------------+
|  key.hashCode()  | -----------------------------------&amp;gt; |  hash = h ^ (h &amp;gt;&amp;gt;&amp;gt; 16)  |
+------------------+                                      +-------------------------+
                                                                       |
                                                                       | Step 2: Index Masking
                                                                       v
                                                          +-------------------------+
                                                          |  index = (n - 1) &amp;amp; hash |
                                                          +-------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 1: The Perturbation Function
&lt;/h3&gt;

&lt;p&gt;The naive approach is to use the key's native &lt;code&gt;hashCode()&lt;/code&gt; directly. However, standard hash codes often vary only in higher bits. Because index calculation ignores higher bits for small map sizes, this causes excessive collisions.&lt;/p&gt;

&lt;p&gt;To mitigate this, the JDK uses a &lt;strong&gt;perturbation function&lt;/strong&gt; to spread the entropy of the higher bits down to the lower bits:&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;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;hash&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Object&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;)&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;h&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;hashCode&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="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;16&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;By shifting the bits right by 16 (&lt;code&gt;h &amp;gt;&amp;gt;&amp;gt; 16&lt;/code&gt;) and performing an XOR operation, we guarantee that the variance in the upper half of the 32-bit integer is mixed into the lower half.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: The Bitwise Modulo Optimization
&lt;/h3&gt;

&lt;p&gt;Normally, mapping a hash to a range &lt;code&gt;[0, n-1]&lt;/code&gt; requires a modulo operation (&lt;code&gt;hash % n&lt;/code&gt;). However, division and modulo operators are computationally expensive on CPU architectures. &lt;/p&gt;

&lt;p&gt;To optimize this, Java enforces that the bucket array size &lt;code&gt;n&lt;/code&gt; &lt;strong&gt;must always be a power of two&lt;/strong&gt;. When &lt;code&gt;n&lt;/code&gt; is a power of two, the mathematical modulo can be written as a bitwise AND operation:&lt;/p&gt;

&lt;p&gt;$$\text{hash} \pmod n = (n - 1) \ &amp;amp; \ \text{hash}$$&lt;/p&gt;

&lt;p&gt;For example, if capacity &lt;code&gt;n = 16&lt;/code&gt; ($2^4$):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;n - 1 = 15&lt;/code&gt;, which is binary &lt;code&gt;0000 0000 0000 1111&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  Performing a bitwise &lt;code&gt;&amp;amp;&lt;/code&gt; acts as a fast mask, isolating only the lowest 4 bits of the hash.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This bitwise operation executes in a single CPU clock cycle, vastly outperforming arithmetic division.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Collision Resolution: Linked Lists to Red-Black Trees
&lt;/h2&gt;

&lt;p&gt;Even with a perfect perturbation function, the Pigeonhole Principle dictates that hash collisions are inevitable. &lt;/p&gt;

&lt;p&gt;In Java 7 and earlier, all collisions were resolved using simple &lt;strong&gt;Separate Chaining&lt;/strong&gt; via singly linked lists. In the worst-case scenario (e.g., all keys having the same bucket index), lookup performance degraded to $O(N)$.&lt;/p&gt;

&lt;h3&gt;
  
  
  Java 8+ Hybrid Approach
&lt;/h3&gt;

&lt;p&gt;Java 8 introduced an optimization to prevent performance degradation under high collision rates. When a bucket's linked list size exceeds a strict threshold, the list is transformed into a &lt;strong&gt;Self-Balancing Red-Black Tree&lt;/strong&gt; (represented by &lt;code&gt;TreeNode&lt;/code&gt;). This reduces the worst-case lookup time from $O(N)$ to $O(\log N)$.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   Bucket Index
     [ ... ]
     [  i  ]  ---&amp;gt;  NodeA (O(N) linked list) ---&amp;gt; NodeB ---&amp;gt; NodeC ...
     [ i+1 ]
     [ i+2 ]  ---&amp;gt;  TreeNode (O(log N) Red-Black Tree)
                    /    \
                 NodeX  NodeY
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This transformation is governed by three critical constants:&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;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="no"&gt;TREEIFY_THRESHOLD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="no"&gt;UNTREEIFY_THRESHOLD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="no"&gt;MIN_TREEIFY_CAPACITY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;TREEIFY_THRESHOLD = 8&lt;/code&gt;&lt;/strong&gt;: If a single bucket exceeds 8 nodes, the map &lt;em&gt;attempts&lt;/em&gt; to treeify the bucket.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;MIN_TREEIFY_CAPACITY = 64&lt;/code&gt;&lt;/strong&gt;: The map will not convert a bucket into a tree unless the overall map capacity is at least 64. If the map capacity is lower, it simply triggers a &lt;code&gt;resize()&lt;/code&gt; operation to spread out the nodes instead. This avoids the high memory overhead of trees for small maps.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;UNTREEIFY_THRESHOLD = 6&lt;/code&gt;&lt;/strong&gt;: During a resize operation, if a tree's node count shrinks to 6 or fewer, it is converted back into a standard linked list to save memory.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why 8? The Poisson Distribution
&lt;/h3&gt;

&lt;p&gt;The choice of &lt;code&gt;TREEIFY_THRESHOLD = 8&lt;/code&gt; is not arbitrary. It is grounded in probability theory. Under a random distribution of hash codes with a default load factor of &lt;code&gt;0.75&lt;/code&gt;, the probability of $k$ items landing in the same bucket follows a Poisson distribution:&lt;/p&gt;

&lt;p&gt;$$P(k) = \frac{e^{-\lambda} \lambda^k}{k!}$$&lt;/p&gt;

&lt;p&gt;The JDK developers calculated these exact probabilities in the &lt;code&gt;HashMap&lt;/code&gt; source documentation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;* 0:    0.60653066
* 1:    0.30326533
* 2:    0.07581633
* 3:    0.01263606
* 4:    0.00157951
* 5:    0.00015795
* 6:    0.00001316
* 7:    0.00000094
* 8:    0.00000006
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The probability of a bucket reaching a size of 8 under normal, non-malicious conditions is &lt;strong&gt;$0.00000006$&lt;/strong&gt; (less than 1 in 10 million). Thus, treeification is designed as a fallback defense mechanism, not a common execution path.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Production Post-Mortem: The HashDoS CPU Starvation
&lt;/h2&gt;

&lt;p&gt;Let's look at a real-world scenario where understanding these internals is the difference between an online platform and an expensive system outage.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Outage Scenario
&lt;/h3&gt;

&lt;p&gt;An e-commerce API gateway consumes a high volume of JSON payloads containing user-submitted preferences. During a peak traffic window, the JVM CPU utilization spiked to 100%, causing health-check timeouts, database connection drops, and cascading service failures across the microservices ecosystem.&lt;/p&gt;

&lt;h3&gt;
  
  
  Thread Dump Investigation
&lt;/h3&gt;

&lt;p&gt;Analyzing the thread dump of the affected JVM containers pointed directly to the culprit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"http-nio-8080-exec-12" #45 daemon prio=5 os_prio=0 cpu=4502.11ms elapsed=12.23s tid=0x00007f3ea0008800 nid=0x1d4a runnable [0x00007f3e843fd000]
   java.lang.Thread.State: RUNNABLE
        at java.util.HashMap.putVal(Project JDK-17)
        at java.util.HashMap.put(Project JDK-17)
        at com.example.gateway.Parser.parsePayload(Parser.java:42)
        ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Several HTTP worker threads were stuck inside &lt;code&gt;HashMap.putVal&lt;/code&gt; consuming maximum CPU.&lt;/p&gt;

&lt;h3&gt;
  
  
  Root Cause: Poor Custom Key Design
&lt;/h3&gt;

&lt;p&gt;The API gateway was parsing incoming JSON keys into custom payload objects to map metadata. Below is the simplified implementation of the custom key class used in the production environment:&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;// VULNERABLE PRODUCTION CODE&lt;/span&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;TenantClientKey&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;tenantId&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;clientId&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;TenantClientKey&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;tenantId&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;clientId&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;tenantId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tenantId&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;clientId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;clientId&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;getTenantId&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;tenantId&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;getClientId&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;clientId&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="nf"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Object&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&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;o&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nc"&gt;TenantClientKey&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="nc"&gt;TenantClientKey&lt;/span&gt; &lt;span class="n"&gt;that&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TenantClientKey&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Objects&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;tenantId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;that&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tenantId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
               &lt;span class="nc"&gt;Objects&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;clientId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;that&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;clientId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&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;hashCode&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// DISASTER: Constant hashcode causes severe collisions!&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;42&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;By returning a constant value of &lt;code&gt;42&lt;/code&gt; for &lt;code&gt;hashCode()&lt;/code&gt;, &lt;strong&gt;every single instance&lt;/strong&gt; of &lt;code&gt;TenantClientKey&lt;/code&gt; mapped to the exact same bucket index inside the &lt;code&gt;HashMap&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;While the application code worked perfectly in low-volume local environments, under load, the &lt;code&gt;HashMap&lt;/code&gt; converted the bucket into a Red-Black Tree. However, treeification requires that tree nodes are sorted. To sort them, &lt;code&gt;HashMap&lt;/code&gt; checks if the keys implement &lt;code&gt;Comparable&lt;/code&gt;. If they do not, it falls back to a comparison helper method (&lt;code&gt;tieBreakOrder&lt;/code&gt;), which relies on the class name and System Identity Hash Code. &lt;/p&gt;

&lt;p&gt;This fallback comparison is highly CPU-intensive. When processing thousands of concurrent user keys, the $O(1)$ operations degraded to $O(\log N)$ with expensive tree rebalancing and fallback comparisons, pushing CPU core consumption to 100%.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Architectural Remediation
&lt;/h2&gt;

&lt;p&gt;To fix this vulnerability, we must implement two key improvements:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;A Balanced, High-Entropy Hashing Scheme&lt;/strong&gt;: Use prime number multipliers to distribute hashes across the 32-bit integer space.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Implement &lt;code&gt;Comparable&lt;/code&gt;&lt;/strong&gt;: Ensure that if a treeification event does happen, the tree nodes can quickly sort themselves without falling back to expensive identity checks.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The Optimized, Defensive Key Class
&lt;/h3&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;TenantClientKey&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;Comparable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;TenantClientKey&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="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;tenantId&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;clientId&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Cache the hash code to avoid repeating calculations (Immutability is key)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;volatile&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;cachedHash&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;TenantClientKey&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;tenantId&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;clientId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tenantId&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;clientId&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&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;"Keys cannot be null"&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;tenantId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tenantId&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;clientId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;clientId&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="nf"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Object&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&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;o&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nc"&gt;TenantClientKey&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="nc"&gt;TenantClientKey&lt;/span&gt; &lt;span class="n"&gt;that&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TenantClientKey&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;o&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;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tenantId&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;that&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tenantId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;clientId&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;that&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;clientId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&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;hashCode&lt;/span&gt;&lt;span class="o"&gt;()&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;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cachedHash&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;h&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// High entropy calculation using prime 31&lt;/span&gt;
            &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;31&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;tenantId&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;hashCode&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
            &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;31&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;clientId&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;hashCode&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
            &lt;span class="n"&gt;cachedHash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;h&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;h&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&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;compareTo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TenantClientKey&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Fast sorting fallback for Red-Black Trees (prevents tieBreakOrder overhead)&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;comp&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;tenantId&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;compareTo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tenantId&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;comp&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;comp&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;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;clientId&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;compareTo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;clientId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  6. Sizing Strategies to Prevent Costly Resizing
&lt;/h2&gt;

&lt;p&gt;When initializing a &lt;code&gt;HashMap&lt;/code&gt;, many developers instantiate it without arguments:&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;Map&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;map&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;HashMap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Bad practice for large maps&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Under the hood, this sets the default capacity to 16. If you insert 10,000 items into this map, it will resize approximately &lt;strong&gt;10 times&lt;/strong&gt; as its threshold is repeatedly crossed ($16 \to 32 \to 64 \dots \to 16384$).&lt;/p&gt;

&lt;p&gt;Resizing requires allocating a new node array that is twice the size and copying every existing item to its new bucket index. This process is highly garbage-collection and CPU-intensive.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Correct Sizing Formula
&lt;/h3&gt;

&lt;p&gt;To prevent resizing, initialize the map with an explicit capacity calculated using the expected size and the load factor:&lt;/p&gt;

&lt;p&gt;$$\text{Initial Capacity} = \left\lceil \frac{\text{Expected Elements}}{\text{Load Factor}} \right\rceil$$&lt;/p&gt;

&lt;p&gt;For example, if you expect $1,000$ items and are using the default load factor of $0.75$:&lt;/p&gt;

&lt;p&gt;$$\text{Initial Capacity} = \frac{1000}{0.75} = 1333.33 \implies 1334$$&lt;/p&gt;

&lt;p&gt;To simplify this, Java 19 introduced a factory method that handles this calculation automatically:&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;// Java 19+ optimized initialization&lt;/span&gt;
&lt;span class="nc"&gt;Map&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;map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HashMap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newHashMap&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For pre-Java 19, use the standard constructor with the manual calculation:&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;// Pre-Java 19 alternative (1334)&lt;/span&gt;
&lt;span class="nc"&gt;Map&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;map&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;HashMap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;(&lt;/span&gt;&lt;span class="mi"&gt;1334&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This simple optimization completely removes resize overhead from the application's runtime hot path.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Architectural Guidelines for Production HashMap Usage
&lt;/h2&gt;

&lt;p&gt;To ensure reliable, high-performance HashMap operations under heavy production loads, keep these guidelines in mind:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Enforce Key Immutability&lt;/strong&gt;: Always declare fields in custom keys as &lt;code&gt;private final&lt;/code&gt;. If a key's fields mutate after it is inserted into a &lt;code&gt;HashMap&lt;/code&gt;, its &lt;code&gt;hashCode&lt;/code&gt; changes. This makes the key impossible to find, creating a memory leak.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Cache the Hash Code&lt;/strong&gt;: If your custom keys are immutable and their hash calculation is expensive, cache the calculated hash code in a private field to save CPU cycles on repeated lookups.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Always Implement &lt;code&gt;Comparable&lt;/code&gt; on Custom Keys&lt;/strong&gt;: If your keys ever end up in a highly congested bucket, implementing &lt;code&gt;Comparable&lt;/code&gt; ensures that the &lt;code&gt;HashMap&lt;/code&gt;'s internal Red-Black tree can search and balance itself efficiently without using CPU-heavy fallback methods.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Avoid Modifying Keys Concurrent to Iteration&lt;/strong&gt;: &lt;code&gt;HashMap&lt;/code&gt; is fail-fast. If you modify the map's structural size while iterating over it (unless using the iterator's own &lt;code&gt;remove&lt;/code&gt; method), it will throw a &lt;code&gt;ConcurrentModificationException&lt;/code&gt;. For concurrent environments, always use &lt;code&gt;ConcurrentHashMap&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>java</category>
      <category>performance</category>
      <category>systemdesign</category>
      <category>backend</category>
    </item>
    <item>
      <title>GraphQL vs. REST Under Load: Architectural Patterns, Pitfalls, and Production-Grade Mitigation</title>
      <dc:creator>Shubham Bhati</dc:creator>
      <pubDate>Sat, 13 Jun 2026 20:59:30 +0000</pubDate>
      <link>https://dev.to/shubham_bhati/graphql-vs-rest-under-load-architectural-patterns-pitfalls-and-production-grade-mitigation-576a</link>
      <guid>https://dev.to/shubham_bhati/graphql-vs-rest-under-load-architectural-patterns-pitfalls-and-production-grade-mitigation-576a</guid>
      <description>&lt;h2&gt;
  
  
  1. The Architectural Divergence Under Load
&lt;/h2&gt;

&lt;p&gt;To understand how these APIs behave under load, we must analyze how they utilize resources like CPU, Memory, and Database Connection Pools.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;REST: Deterministic &amp;amp; Predictable Execution
[Client] ---&amp;gt; GET /api/v1/users/123 ---&amp;gt; [Server] ---&amp;gt; Single SELECT Query ---&amp;gt; [DB]
             (Static Path, HTTP Cacheable)

GraphQL: Dynamic &amp;amp; Non-Deterministic Execution
[Client] ---&amp;gt; POST /graphql  ------------&amp;gt; [Server] ---&amp;gt; AST Parsing &amp;amp; Validation
              { user { orders { items } } }             ---&amp;gt; Resolvers &amp;amp; N+1 Queries? ---&amp;gt; [DB]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;REST (Representational State Transfer)&lt;/strong&gt; decouples resources into strict, addressable URIs. Under load, execution paths are highly predictable. A gateway can inspect the path &lt;code&gt;/api/v1/products/{id}&lt;/code&gt;, route it to a specific microservice pool, and cache the payload at the CDN edge using standard HTTP headers (&lt;code&gt;Cache-Control&lt;/code&gt;, &lt;code&gt;ETag&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;GraphQL&lt;/strong&gt; routes all interactions through a single &lt;code&gt;/graphql&lt;/code&gt; endpoint (usually via HTTP &lt;code&gt;POST&lt;/code&gt;). The server receives an arbitrary query string, parses it into an Abstract Syntax Tree (AST), validates it against a schema, and executes resolver functions dynamically. Under load, this dynamic execution introduces non-deterministic query paths, rendering standard HTTP gateway caching useless and moving the computation bottleneck from the network to the server's CPU and database.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  2. GraphQL Failure Mode 1: The Cascading N+1 Database Exhaustion
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Scenario
&lt;/h3&gt;

&lt;p&gt;You deploy a highly nested GraphQL schema representing an e-commerce platform. Under load (5,000 RPS), the database connection pool is exhausted within seconds. Response times spike from 50ms to timeouts (&lt;code&gt;504 Gateway Timeout&lt;/code&gt;), and CPU utilization on the database reaches 100%.&lt;/p&gt;

&lt;h3&gt;
  
  
  Root Cause Analysis
&lt;/h3&gt;

&lt;p&gt;Without optimization, a GraphQL query executing nested fields runs a resolver function for &lt;em&gt;each&lt;/em&gt; node in the graph. If a client queries $N$ users and their corresponding orders, the engine first fetches the users (1 query) and then invokes the &lt;code&gt;orders&lt;/code&gt; resolver for &lt;em&gt;each&lt;/em&gt; of the $N$ users ($N$ queries).&lt;/p&gt;

&lt;p&gt;This is the classic &lt;strong&gt;N+1 Query Problem&lt;/strong&gt;. In a high-concurrency environment, this triggers an exponential explosion of database queries, completely draining your HikariCP connection pool.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Solution: Batching and Caching with Spring GraphQL DataLoaders
&lt;/h3&gt;

&lt;p&gt;To mitigate this, we must intercept nested queries and batch them into a single, cohesive database call using the &lt;code&gt;DataLoader&lt;/code&gt; pattern. This converts $N+1$ SQL queries into exactly $2$ queries.&lt;/p&gt;

&lt;h4&gt;
  
  
  Vulnerable Resolver Code (Anti-Pattern)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Controller&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;UserGraphQLController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@QueryMapping&lt;/span&gt;
    &lt;span class="kd"&gt;public&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;User&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;users&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;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="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@SchemaMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;typeName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"User"&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="s"&gt;"orders"&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;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;orders&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="c1"&gt;// Triggers N times! For 100 users, this executes 100 separate DB queries.&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;findAllByUserId&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="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Production-Grade Mitigated Code (Spring Boot 3 + Spring GraphQL)
&lt;/h4&gt;

&lt;p&gt;We leverage &lt;code&gt;@BatchMapping&lt;/code&gt; to automatically register a batch loader that collects user IDs across the execution context and resolves their orders in a single &lt;code&gt;IN&lt;/code&gt; query.&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;package&lt;/span&gt; &lt;span class="nn"&gt;com.architecture.api.controller&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.architecture.api.model.Order&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.architecture.api.model.User&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.architecture.api.repository.OrderRepository&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.architecture.api.repository.UserRepository&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.slf4j.Logger&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.slf4j.LoggerFactory&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.graphql.data.method.annotation.BatchMapping&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.graphql.data.method.annotation.QueryMapping&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.stereotype.Controller&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;reactor.core.publisher.Mono&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.List&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.Map&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.stream.Collectors&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@Controller&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;ResilientUserController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Logger&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LoggerFactory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getLogger&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ResilientUserController&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="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;UserRepository&lt;/span&gt; &lt;span class="n"&gt;userRepository&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;OrderRepository&lt;/span&gt; &lt;span class="n"&gt;orderRepository&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;ResilientUserController&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserRepository&lt;/span&gt; &lt;span class="n"&gt;userRepository&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;OrderRepository&lt;/span&gt; &lt;span class="n"&gt;orderRepository&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;userRepository&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;userRepository&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;orderRepository&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="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@QueryMapping&lt;/span&gt;
    &lt;span class="kd"&gt;public&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;User&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;users&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Fetching all active users"&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;findAll&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; 
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Replaces the N+1 resolver. Spring GraphQL intercepts the execution,
     * aggregates the User entities, and executes this single batch mapping.
     */&lt;/span&gt;
    &lt;span class="nd"&gt;@BatchMapping&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="s"&gt;"orders"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;typeName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"User"&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;Mono&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Map&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;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;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;loadOrders&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;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="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Batch loading orders for {} users to prevent N+1"&lt;/span&gt;&lt;span class="o"&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;size&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;Long&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;userIds&lt;/span&gt; &lt;span class="o"&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;stream&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;User:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;getId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;collect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Collectors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toList&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

        &lt;span class="c1"&gt;// Single SQL Query: SELECT * FROM orders WHERE user_id IN (?, ?, ...)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Mono&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromCallable&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;orderRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findAllByUserIdIn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userIds&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                    &lt;span class="c1"&gt;// Group the orders by User object to satisfy the Batch Mapping contract&lt;/span&gt;
                    &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Long&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;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ordersByUserId&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="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;collect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Collectors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;groupingBy&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;Order:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;getUserId&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;users&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;collect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Collectors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toMap&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;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;user&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;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ordersByUserId&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getOrDefault&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="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
                    &lt;span class="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;h2&gt;
  
  
  3. GraphQL Failure Mode 2: The "Query of Death" (Denial of Wallet)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Scenario
&lt;/h3&gt;

&lt;p&gt;An attacker or a poorly configured client application sends a highly nested self-referential query (e.g., &lt;code&gt;User -&amp;gt; Friends -&amp;gt; Friends -&amp;gt; Friends...&lt;/code&gt;) or a query requesting thousands of fields. The application server attempts to parse, validate, and construct an AST for this massive payload, running out of memory (JVM &lt;code&gt;OutOfMemoryError&lt;/code&gt;) and crashing the entire container instance.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="c"&gt;# The Query of Death&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;query&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;maliciousQuery&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="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1"&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="n"&gt;friends&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="n"&gt;friends&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="n"&gt;friends&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="n"&gt;friends&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="c"&gt;# ... repeated 50 times&lt;/span&gt;&lt;span class="w"&gt;
             &lt;/span&gt;&lt;span class="n"&gt;name&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="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="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="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Root Cause Analysis
&lt;/h3&gt;

&lt;p&gt;The GraphQL engine parses &lt;em&gt;every&lt;/em&gt; received query dynamically into an Abstract Syntax Tree (AST) before validation. This parsing process is CPU and memory-intensive. Unbounded query depth and schema complexity allow clients to easily force the server to execute exponential computing tasks.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Solution: Static Query Depth and Complexity Analysis
&lt;/h3&gt;

&lt;p&gt;We must intercept the query lifecycle and reject queries that exceed safe complexity and depth thresholds &lt;em&gt;before&lt;/em&gt; execution begins.&lt;/p&gt;

&lt;h4&gt;
  
  
  Production-Grade Mitigation Config (Spring Boot)
&lt;/h4&gt;

&lt;p&gt;We can inject dynamic validation rules directly into our GraphQL execution engine config using &lt;code&gt;graphql-java&lt;/code&gt; instrumentations.&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;package&lt;/span&gt; &lt;span class="nn"&gt;com.architecture.api.config&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;graphql.analysis.MaxQueryComplexityInstrumentation&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;graphql.analysis.MaxQueryDepthInstrumentation&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;graphql.execution.instrumentation.ChainedInstrumentation&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;graphql.execution.instrumentation.Instrumentation&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.context.annotation.Bean&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.context.annotation.Configuration&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.List&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@Configuration&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;GraphQLSecurityConfig&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="no"&gt;MAX_DEPTH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="no"&gt;MAX_COMPLEXITY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Bean&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Instrumentation&lt;/span&gt; &lt;span class="nf"&gt;graphQLInstrumentations&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// MaxQueryDepthInstrumentation: Restricts the nesting level&lt;/span&gt;
        &lt;span class="nc"&gt;Instrumentation&lt;/span&gt; &lt;span class="n"&gt;depthInstrumentation&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;MaxQueryDepthInstrumentation&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;MAX_DEPTH&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// MaxQueryComplexityInstrumentation: Restricts total fields/nodes requested&lt;/span&gt;
        &lt;span class="c1"&gt;// Each field defaults to a complexity score of 1.&lt;/span&gt;
        &lt;span class="nc"&gt;Instrumentation&lt;/span&gt; &lt;span class="n"&gt;complexityInstrumentation&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;MaxQueryComplexityInstrumentation&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;MAX_COMPLEXITY&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;ChainedInstrumentation&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;depthInstrumentation&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;complexityInstrumentation&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;With this configuration in place, malicious nested queries are rejected immediately with a validation error during the compilation phase, saving CPU and DB resources.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. REST Failure Mode: Under-Fetching &amp;amp; Chatty Network Overhead
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Scenario
&lt;/h3&gt;

&lt;p&gt;To render a dashboard page, a client application needs data from several related entities: the current user profile, their active subscriptions, recent transactions, and recommended products. &lt;/p&gt;

&lt;p&gt;Under high load on mobile networks, the application experiences extreme latency. The server CPU is underutilized, but the HTTP network connection pool on the API gateway is saturated with pending client connections, leading to thread pool starvation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Chatty REST Pattern (Under-fetching)
[Client] ---&amp;gt; GET /api/v1/users/me -------------&amp;gt; [Server] (Success 200)
[Client] ---&amp;gt; GET /api/v1/subscriptions/{id} ----&amp;gt; [Server] (Success 200)
[Client] ---&amp;gt; GET /api/v1/transactions --------- -&amp;gt; [Server] (Success 200)
[Client] ---&amp;gt; GET /api/v1/recommendations --------&amp;gt; [Server] (Success 200)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Root Cause Analysis
&lt;/h3&gt;

&lt;p&gt;This is the &lt;strong&gt;Under-fetching / Chatty Client problem&lt;/strong&gt;. Because REST endpoints are static and resource-focused, clients must execute multiple sequential or parallel HTTP requests to compile a single logical view. &lt;/p&gt;

&lt;p&gt;Under load, this results in:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;TCP/TLS Handshake Overhead:&lt;/strong&gt; Multiple connections compete for bandwidth.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Thread Starvation:&lt;/strong&gt; Each HTTP request consumes a worker thread (in thread-per-request engines like Spring MVC / Tomcat) or keeps async connections open longer on the gateway.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Head-of-Line Blocking.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The Solution: Implement Dynamic Projection &amp;amp; JSON-API Sparse Fieldsets
&lt;/h3&gt;

&lt;p&gt;To solve this in REST without shifting to GraphQL, we can build dynamic projection support directly into our REST controllers using Jackson Mixins or dynamic filtering. This allows the client to explicitly request only the fields and nested relations they need in a single HTTP round-trip.&lt;/p&gt;

&lt;h4&gt;
  
  
  Production-Grade Dynamic Projection implementation (Spring Boot)
&lt;/h4&gt;

&lt;p&gt;We will implement an API that accepts a &lt;code&gt;?fields=&lt;/code&gt; query parameter and filters the JSON response dynamically at the serialization layer.&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;package&lt;/span&gt; &lt;span class="nn"&gt;com.architecture.api.controller&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.architecture.api.model.UserProfile&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.fasterxml.jackson.databind.ser.FilterProvider&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.http.converter.json.MappingJacksonValue&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.web.bind.annotation.GetMapping&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.web.bind.annotation.RequestParam&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.web.bind.annotation.RestController&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.Set&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@RestController&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;DynamicProfileController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Dynamic REST Endpoint simulating "GraphQL-like" selectivity.
     * Request Example: GET /api/v1/profiles/me?fields=username,email
     */&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;"/api/v1/profiles/me"&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;MappingJacksonValue&lt;/span&gt; &lt;span class="nf"&gt;getProfile&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@RequestParam&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&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;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;UserProfile&lt;/span&gt; &lt;span class="n"&gt;profile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fetchUserProfileFromDatabase&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Wrap the object to apply dynamic Jackson filtering&lt;/span&gt;
        &lt;span class="nc"&gt;MappingJacksonValue&lt;/span&gt; &lt;span class="n"&gt;wrapper&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;MappingJacksonValue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="nc"&gt;FilterProvider&lt;/span&gt; &lt;span class="n"&gt;filterProvider&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;fields&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isEmpty&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Default behavior: Serialize everything&lt;/span&gt;
            &lt;span class="n"&gt;filterProvider&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;SimpleFilterProvider&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;addFilter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                    &lt;span class="s"&gt;"DynamicProfileFilter"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;SimpleBeanPropertyFilter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;serializeAll&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="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// High-Performance Filter: Serialize ONLY requested fields&lt;/span&gt;
            &lt;span class="n"&gt;filterProvider&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;SimpleFilterProvider&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;addFilter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                    &lt;span class="s"&gt;"DynamicProfileFilter"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;SimpleBeanPropertyFilter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;filterOutAllExcept&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fields&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="n"&gt;wrapper&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setFilters&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filterProvider&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;wrapper&lt;/span&gt;&lt;span class="o"&gt;;&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;UserProfile&lt;/span&gt; &lt;span class="nf"&gt;fetchUserProfileFromDatabase&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;UserProfile&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1L&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"john_doe"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"john@example.com"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Tier-1 VIP"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"0x345678"&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;And configure the target Entity to support dynamic filtering:&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;package&lt;/span&gt; &lt;span class="nn"&gt;com.architecture.api.model&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.fasterxml.jackson.annotation.JsonFilter&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@JsonFilter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"DynamicProfileFilter"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Bound directly to our dynamic projection controller&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;UserProfile&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;Long&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="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="kd"&gt;private&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="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;statusTier&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;secureApiKey&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Sensitive data we can dynamically omit&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;UserProfile&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="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;statusTier&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;secureApiKey&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;id&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="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;username&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;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;email&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;statusTier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;statusTier&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;secureApiKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;secureApiKey&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Getters and Setters ...&lt;/span&gt;
    &lt;span class="kd"&gt;public&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="o"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&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="kd"&gt;public&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;username&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;getEmail&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;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="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;getStatusTier&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;statusTier&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;getSecureApiKey&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;secureApiKey&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  5. Performance &amp;amp; Caching Comparison Under Load
&lt;/h2&gt;

&lt;p&gt;Caching is the primary defense mechanism against load. Let's compare the caching capabilities of both paradigms:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Caching Dimension&lt;/th&gt;
&lt;th&gt;REST&lt;/th&gt;
&lt;th&gt;GraphQL&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Edge/CDN Caching (HTTP GET)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Out-of-the-Box (Excellent).&lt;/strong&gt; Uses standard &lt;code&gt;Cache-Control&lt;/code&gt;, &lt;code&gt;ETag&lt;/code&gt;, and &lt;code&gt;Vary&lt;/code&gt; headers. Edge nodes (Cloudflare, Fastly) absorb traffic without touching backend servers.&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Challenging (Poor).&lt;/strong&gt; Because most queries are HTTP &lt;code&gt;POST&lt;/code&gt; payloads, standard CDNs cannot inspect the body to cache responses natively.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Server-Side Application Caching&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Resource-level.&lt;/strong&gt; Straightforward key-value mapping (e.g., Redis key &lt;code&gt;user:123&lt;/code&gt; mapped to User JSON).&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Graph-level.&lt;/strong&gt; Complex object graphs make invalidation extremely difficult. Changing a single nested node requires invalidating multiple overlapping graph queries.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Client Caching&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Simple URL-key mapping.&lt;/td&gt;
&lt;td&gt;Normalized cache stores (e.g., Apollo Client InMemoryCache) requiring unique IDs (&lt;code&gt;__typename:id&lt;/code&gt;) across all entities.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Designing GraphQL for Edge Caching: Automatic Persisted Queries (APQ)
&lt;/h3&gt;

&lt;p&gt;To leverage CDN caching for GraphQL, we can use &lt;strong&gt;Automatic Persisted Queries (APQ)&lt;/strong&gt;. APQ works by sending a deterministic hash of the query via an HTTP &lt;code&gt;GET&lt;/code&gt; request, converting dynamic GraphQL queries into predictable, cacheable GET paths.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;APQ Negotiation Flow:
1. Client hashes query: SHA256("query { user { name } }") = "abc123hash"
2. Client sends: GET /graphql?extensions={"persistedQuery":{"version":1,"sha256Hash":"abc123hash"}}
3. If Cache Miss: Server requests full query body, executes, and saves hash to Redis.
4. If Cache Hit: CDN/Server immediately resolves and serves response.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  6. The Architect's Decision Matrix
&lt;/h2&gt;

&lt;p&gt;To ensure your system remains stable under peak traffic, select your API paradigm based on the following engineering trade-offs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                  ┌──────────────────────────────┐
                  │   Are client data needs      │
                  │   highly polymorphic/fluid?  │
                  └──────────────┬───────────────┘
                                 │
                   ┌─────────────┴─────────────┐
                   ▼ Yes                       ▼ No
     ┌───────────────────────────┐       ┌───────────────────────────┐
     │ Is CDN edge-caching the   │       │ REST is the optimal choice│
     │ primary path to scale?    │       │ Use HTTP GET Caching,     │
     └─────────────┬─────────────┘       │ static route optimization │
                   │                     └───────────────────────────┘
     ┌─────────────┴─────────────┐
     ▼ Yes                       ▼ No
┌───────────────────────────┐   ┌───────────────────────────┐
│ Use GraphQL with APQ,      │   │ GraphQL with DataLoaders, │
│ query depth analysis,     │   │ query complexity limits,  │
│ &amp;amp; CDN-compatible GETs     │   │ and robust Redis caching  │
└───────────────────────────┘   └───────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  When to Standardize on REST
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Read-Heavy Public APIs:&lt;/strong&gt; If your traffic is dominated by predictable reads (e.g., public catalogs, news feeds), REST's native HTTP/CDN caching capabilities will absorb 95%+ of the load long before it hits your database.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Low CPU Budgets:&lt;/strong&gt; REST avoids the overhead of query parsing, validation, and execution-context creation.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Strict Security Compliance:&lt;/strong&gt; REST allows you to easily attach role-based access control (RBAC) to specific URIs at the API gateway layer.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  When to Standardize on GraphQL
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Complex, Client-Heavy Operations:&lt;/strong&gt; Mobile applications or administrative dashboards requiring dynamic aggregation across multiple highly-relational data sources.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Bandwidth-Constrained Networks:&lt;/strong&gt; If network payload size is your primary bottleneck, GraphQL's sparse selection minimizes the data transferred over the wire.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Strictly Monitored Execution:&lt;/strong&gt; If you deploy a centralized Apollo router/gateway that handles query orchestration, dynamic routing, and query federation across a fleet of internal microservices.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Regardless of your chosen architectural pattern, scaling under load boils down to &lt;strong&gt;determinism&lt;/strong&gt;. For REST, this means designing lightweight, deterministic resource projections. For GraphQL, this means enforcing static query constraints, implementing batching mechanisms like DataLoaders, and mapping execution payloads into cacheable queries.&lt;/p&gt;

</description>
      <category>systemdesign</category>
      <category>graphql</category>
      <category>springboot</category>
      <category>performance</category>
    </item>
    <item>
      <title>Asynchronous Programming in Spring Boot with CompletableFuture</title>
      <dc:creator>Shubham Bhati</dc:creator>
      <pubDate>Thu, 11 Jun 2026 08:21:17 +0000</pubDate>
      <link>https://dev.to/shubham_bhati/asynchronous-programming-in-spring-boot-with-completablefuture-3elj</link>
      <guid>https://dev.to/shubham_bhati/asynchronous-programming-in-spring-boot-with-completablefuture-3elj</guid>
      <description>&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%2Fsource.unsplash.com%2F1200x630%2F%3Fasync%2Cprogramming%2Cjava%26sig%3D1" 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%2Fsource.unsplash.com%2F1200x630%2F%3Fasync%2Cprogramming%2Cjava%26sig%3D1" alt="Spring Boot Async Completablefuture" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Published 2026-06-11 by &lt;a href="https://shubh2-0.github.io" rel="noopener noreferrer"&gt;Shubham Bhati&lt;/a&gt; — Backend Engineer (Java 17, Spring Boot, Microservices).&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We've all been there - stuck with a slow-performing API endpoint that's causing our application to grind to a halt. In our production environment, we recently encountered an issue where a single endpoint was taking up to 800ms to respond, causing our p99 latency to skyrocket. After some digging, we discovered that the culprit was a synchronous call to an external service. By switching to asynchronous programming using Spring Boot and &lt;code&gt;CompletableFuture&lt;/code&gt;, we were able to reduce our p99 latency from 800ms to 120ms. This experience taught us the importance of using &lt;code&gt;spring boot async CompletableFuture&lt;/code&gt; to write non-blocking code.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Introduction to Asynchronous Programming&lt;/li&gt;
&lt;li&gt;Using the &lt;code&gt;@Async&lt;/code&gt; Annotation&lt;/li&gt;
&lt;li&gt;Working with &lt;code&gt;CompletableFuture&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Configuring Thread Pools&lt;/li&gt;
&lt;li&gt;Handling Errors and Exceptions&lt;/li&gt;
&lt;li&gt;Common Mistakes&lt;/li&gt;
&lt;li&gt;FAQ&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Introduction to Asynchronous Programming
&lt;/h2&gt;

&lt;p&gt;Asynchronous programming is a technique that allows our application to perform multiple tasks concurrently, improving overall performance and responsiveness. In Java, we can use the &lt;code&gt;java.util.concurrent&lt;/code&gt; package to write asynchronous code. Spring Boot provides additional support for asynchronous programming through the &lt;code&gt;@Async&lt;/code&gt; annotation and &lt;code&gt;CompletableFuture&lt;/code&gt;. By using these tools, we can write non-blocking code that's easier to maintain and scales better.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using the &lt;code&gt;@Async&lt;/code&gt; Annotation
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;@Async&lt;/code&gt; annotation is a convenient way to mark a method as asynchronous. When we annotate a method with &lt;code&gt;@Async&lt;/code&gt;, Spring Boot will automatically execute it in a separate thread. Here's an 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="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;MyService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Async&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;CompletableFuture&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="nf"&gt;doSomethingAsync&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Simulate some long-running operation&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sleep&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="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;InterruptedException&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;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;currentThread&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;interrupt&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="nc"&gt;CompletableFuture&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;completedFuture&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Done"&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;In this example, the &lt;code&gt;doSomethingAsync&lt;/code&gt; method is annotated with &lt;code&gt;@Async&lt;/code&gt;, indicating that it should be executed asynchronously.&lt;/p&gt;

&lt;h2&gt;
  
  
  Working with &lt;code&gt;CompletableFuture&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;CompletableFuture&lt;/code&gt; is a powerful tool for working with asynchronous operations. It provides a flexible way to compose and combine multiple asynchronous tasks. Here's an 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="nc"&gt;CompletableFuture&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="nf"&gt;doSomethingAsync&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;CompletableFuture&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;future&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CompletableFuture&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;supplyAsync&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Simulate some long-running operation&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sleep&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="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;InterruptedException&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;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;currentThread&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;interrupt&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="s"&gt;"Done"&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;future&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;In this example, we use &lt;code&gt;CompletableFuture.supplyAsync&lt;/code&gt; to create a new &lt;code&gt;CompletableFuture&lt;/code&gt; instance that represents an asynchronous operation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring Thread Pools
&lt;/h2&gt;

&lt;p&gt;When working with asynchronous programming, it's essential to configure thread pools properly. By default, Spring Boot uses a single thread pool with a fixed size of 10 threads. However, we can customize this behavior by creating a custom thread pool configuration. Here's an 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="nd"&gt;@Configuration&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;ThreadPoolConfig&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Bean&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;TaskExecutor&lt;/span&gt; &lt;span class="nf"&gt;taskExecutor&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;ThreadPoolTaskExecutor&lt;/span&gt; &lt;span class="n"&gt;executor&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;ThreadPoolTaskExecutor&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setCorePoolSize&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="n"&gt;executor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setMaxPoolSize&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setQueueCapacity&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setThreadNamePrefix&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"my-thread-"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;initialize&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;executor&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;In this example, we create a custom thread pool configuration with a core pool size of 10 threads and a maximum pool size of 20 threads.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling Errors and Exceptions
&lt;/h2&gt;

&lt;p&gt;When working with asynchronous programming, it's crucial to handle errors and exceptions properly. We can use &lt;code&gt;try-catch&lt;/code&gt; blocks to catch and handle exceptions, but we can also use &lt;code&gt;CompletableFuture.exceptionally&lt;/code&gt; to handle exceptions in a more elegant way. Here's an 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="nc"&gt;CompletableFuture&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="nf"&gt;doSomethingAsync&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;CompletableFuture&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;future&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CompletableFuture&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;supplyAsync&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Simulate some long-running operation&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sleep&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="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;InterruptedException&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;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;currentThread&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;interrupt&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="s"&gt;"Done"&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;future&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;exceptionally&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Handle the exception&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"Error"&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;In this example, we use &lt;code&gt;CompletableFuture.exceptionally&lt;/code&gt; to handle exceptions in a more elegant way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling Cancellation
&lt;/h2&gt;

&lt;p&gt;When working with asynchronous programming, it's essential to handle cancellation properly. We can use &lt;code&gt;CompletableFuture.cancel&lt;/code&gt; to cancel an ongoing asynchronous operation. Here's an 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="nc"&gt;CompletableFuture&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="nf"&gt;doSomethingAsync&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;CompletableFuture&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;future&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CompletableFuture&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;supplyAsync&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Simulate some long-running operation&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sleep&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="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;InterruptedException&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;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;currentThread&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;interrupt&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="s"&gt;"Done"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;});&lt;/span&gt;
    &lt;span class="c1"&gt;// Cancel the operation&lt;/span&gt;
    &lt;span class="n"&gt;future&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;cancel&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;future&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;In this example, we use &lt;code&gt;CompletableFuture.cancel&lt;/code&gt; to cancel an ongoing asynchronous operation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Mistakes
&lt;/h2&gt;

&lt;p&gt;Here are some common mistakes to avoid when working with asynchronous programming:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Not using &lt;code&gt;@Async&lt;/code&gt; annotation correctly&lt;/li&gt;
&lt;li&gt;Not configuring thread pools properly&lt;/li&gt;
&lt;li&gt;Not handling errors and exceptions correctly&lt;/li&gt;
&lt;li&gt;Not using &lt;code&gt;CompletableFuture&lt;/code&gt; correctly&lt;/li&gt;
&lt;li&gt;Not testing asynchronous code thoroughly&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is the difference between &lt;code&gt;@Async&lt;/code&gt; and &lt;code&gt;CompletableFuture&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;@Async&lt;/code&gt; is an annotation that marks a method as asynchronous, while &lt;code&gt;CompletableFuture&lt;/code&gt; is a class that represents an asynchronous operation. We can use &lt;code&gt;@Async&lt;/code&gt; to mark a method as asynchronous, and then use &lt;code&gt;CompletableFuture&lt;/code&gt; to work with the resulting asynchronous operation.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I configure thread pools in Spring Boot?
&lt;/h3&gt;

&lt;p&gt;We can configure thread pools in Spring Boot by creating a custom thread pool configuration using the &lt;code&gt;ThreadPoolTaskExecutor&lt;/code&gt; class. We can also use the &lt;code&gt;@Bean&lt;/code&gt; annotation to define a custom thread pool configuration.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the best way to handle errors and exceptions in asynchronous programming?
&lt;/h3&gt;

&lt;p&gt;The best way to handle errors and exceptions in asynchronous programming is to use &lt;code&gt;try-catch&lt;/code&gt; blocks to catch and handle exceptions, and then use &lt;code&gt;CompletableFuture.exceptionally&lt;/code&gt; to handle exceptions in a more elegant way.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I test asynchronous code?
&lt;/h3&gt;

&lt;p&gt;We can test asynchronous code by using testing frameworks such as JUnit or TestNG, and then using mock objects to simulate asynchronous operations. We can also use tools such as &lt;a href="https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-testing" rel="noopener noreferrer"&gt;Spring Boot Test&lt;/a&gt; to test asynchronous code.&lt;/p&gt;

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

&lt;p&gt;In conclusion, asynchronous programming is a powerful technique that can improve the performance and responsiveness of our application. By using &lt;code&gt;spring boot async CompletableFuture&lt;/code&gt;, we can write non-blocking code that's easier to maintain and scales better. We can also use &lt;code&gt;@Async&lt;/code&gt; annotation and &lt;code&gt;CompletableFuture&lt;/code&gt; to work with asynchronous operations, and then configure thread pools and handle errors and exceptions correctly. For more information, we can refer to the &lt;a href="https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-async" rel="noopener noreferrer"&gt;Spring Boot documentation&lt;/a&gt; or the &lt;a href="https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html" rel="noopener noreferrer"&gt;Java documentation&lt;/a&gt;.&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%2Fsource.unsplash.com%2F1000x500%2F%3Fasync%2Cprogramming%2Cjava%26sig%3D2" 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%2Fsource.unsplash.com%2F1000x500%2F%3Fasync%2Cprogramming%2Cjava%26sig%3D2" alt="Spring Boot Async Completablefuture in production" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/" rel="noopener noreferrer"&gt;Spring Boot Official Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.baeldung.com/" rel="noopener noreferrer"&gt;Baeldung — Java &amp;amp; Spring Tutorials&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.oracle.com/en/java/" rel="noopener noreferrer"&gt;Oracle Java Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Written by **Shubham Bhati&lt;/em&gt;* — Backend Engineer at AlignBits LLC, specializing in Java 17, Spring Boot, microservices, and AI integration. Connect on &lt;a href="https://linkedin.com/in/bhatishubham" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;, &lt;a href="https://github.com/Shubh2-0" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, or read more at &lt;a href="https://shubh2-0.github.io" rel="noopener noreferrer"&gt;shubh2-0.github.io&lt;/a&gt;.*&lt;/p&gt;

</description>
      <category>java</category>
      <category>springboot</category>
      <category>async</category>
      <category>concurrency</category>
    </item>
    <item>
      <title>Service Discovery with Eureka and Spring Cloud: A Hands-On Tutorial</title>
      <dc:creator>Shubham Bhati</dc:creator>
      <pubDate>Mon, 08 Jun 2026 08:27:09 +0000</pubDate>
      <link>https://dev.to/shubham_bhati/service-discovery-with-eureka-and-spring-cloud-a-hands-on-tutorial-1o60</link>
      <guid>https://dev.to/shubham_bhati/service-discovery-with-eureka-and-spring-cloud-a-hands-on-tutorial-1o60</guid>
      <description>&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%2Fsource.unsplash.com%2F1200x630%2F%3Fmicroservices%2Cnetwork%2Cdiscovery%26sig%3D1" 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%2Fsource.unsplash.com%2F1200x630%2F%3Fmicroservices%2Cnetwork%2Cdiscovery%26sig%3D1" alt="Spring Cloud Eureka Tutorial" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Published 2026-06-08 by &lt;a href="https://shubh2-0.github.io" rel="noopener noreferrer"&gt;Shubham Bhati&lt;/a&gt; — Backend Engineer (Java 17, Spring Boot, Microservices).&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We've all been there - trying to manage a fleet of microservices, each with its own instance and endpoint. As our application grew, we found ourselves struggling to keep track of which service was running on which port, and how to route requests between them. That's when we discovered the power of service discovery with Spring Cloud Eureka. In this tutorial, we'll take a hands-on look at how to implement a Spring Cloud Eureka tutorial in your own application, and explore the benefits of using this powerful tool for service discovery.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Introduction to Service Discovery&lt;/li&gt;
&lt;li&gt;Setting Up Spring Cloud Eureka&lt;/li&gt;
&lt;li&gt;Registering Services with Eureka&lt;/li&gt;
&lt;li&gt;Using Eureka for Service Discovery&lt;/li&gt;
&lt;li&gt;Configuring Eureka for High Availability&lt;/li&gt;
&lt;li&gt;Common Mistakes&lt;/li&gt;
&lt;li&gt;FAQ&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Introduction to Service Discovery
&lt;/h2&gt;

&lt;p&gt;Service discovery is a critical component of any microservices architecture. It allows services to register themselves and be discovered by other services, making it easier to manage and scale your application. With Spring Cloud Eureka, we can easily implement service discovery in our application. We've seen significant benefits from using Eureka in production, including reduced latency and improved scalability. For example, we were able to reduce our p99 latency from 800ms to 120ms by using Eureka to route requests to the nearest available service instance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up Spring Cloud Eureka
&lt;/h2&gt;

&lt;p&gt;To get started with Spring Cloud Eureka, we need to add the following dependencies to our &lt;code&gt;pom.xml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework.cloud&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-cloud-starter-netflix-eureka-server&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can then create a Eureka server by annotating our application class with &lt;code&gt;@EnableEurekaServer&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@SpringBootApplication&lt;/span&gt;
&lt;span class="nd"&gt;@EnableEurekaServer&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;EurekaServerApplication&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;SpringApplication&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;EurekaServerApplication&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="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="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For more information on setting up Spring Cloud Eureka, we recommend checking out the &lt;a href="https://docs.spring.io/spring-cloud-netflix/docs/3.1.4/reference/html/#service-discovery-eureka" rel="noopener noreferrer"&gt;official Spring documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Registering Services with Eureka
&lt;/h2&gt;

&lt;p&gt;Once we have our Eureka server up and running, we can start registering our services with it. We can do this by adding the &lt;code&gt;spring-cloud-starter-netflix-eureka-client&lt;/code&gt; dependency to our service's &lt;code&gt;pom.xml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework.cloud&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-cloud-starter-netflix-eureka-client&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can then register our service with Eureka by annotating our application class with &lt;code&gt;@EnableDiscoveryClient&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@SpringBootApplication&lt;/span&gt;
&lt;span class="nd"&gt;@EnableDiscoveryClient&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;ServiceApplication&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;SpringApplication&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ServiceApplication&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="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="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For more information on registering services with Eureka, we recommend checking out the &lt;a href="https://www.baeldung.com/spring-cloud-netflix-eureka" rel="noopener noreferrer"&gt;Baeldung tutorial on Eureka&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Eureka for Service Discovery
&lt;/h2&gt;

&lt;p&gt;Once our services are registered with Eureka, we can start using it for service discovery. We can do this by using the &lt;code&gt;DiscoveryClient&lt;/code&gt; interface to retrieve a list of available service instances:&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;@RestController&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;ServiceController&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;DiscoveryClient&lt;/span&gt; &lt;span class="n"&gt;discoveryClient&lt;/span&gt;&lt;span class="o"&gt;;&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;"/services"&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;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ServiceInstance&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;getServices&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;discoveryClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getInstances&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"my-service"&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;We can then use this list of service instances to route requests to the nearest available service instance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring Eureka for High Availability
&lt;/h2&gt;

&lt;p&gt;To configure Eureka for high availability, we need to set up multiple Eureka servers and configure our services to register with all of them. We can do this by setting the &lt;code&gt;eureka.client.service-url.defaultZone&lt;/code&gt; property to a comma-separated list of Eureka server URLs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;eureka.client.service-url.defaultZone&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;http://eureka1:8761/eureka,http://eureka2:8761/eureka&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can also configure our Eureka servers to replicate their registry with each other by setting the &lt;code&gt;eureka.server.peer-eureka-nodes-update-clone-connections&lt;/code&gt; property to &lt;code&gt;true&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;eureka.server.peer-eureka-nodes-update-clone-connections&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For more information on configuring Eureka for high availability, we recommend checking out the &lt;a href="https://docs.spring.io/spring-cloud-netflix/docs/3.1.4/reference/html/#service-discovery-eureka" rel="noopener noreferrer"&gt;official Spring documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Mistakes
&lt;/h2&gt;

&lt;p&gt;Here are some common mistakes to watch out for when using Spring Cloud Eureka:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Not configuring the &lt;code&gt;eureka.client.service-url.defaultZone&lt;/code&gt; property correctly&lt;/li&gt;
&lt;li&gt;Not setting up multiple Eureka servers for high availability&lt;/li&gt;
&lt;li&gt;Not configuring the &lt;code&gt;eureka.server.peer-eureka-nodes-update-clone-connections&lt;/code&gt; property correctly&lt;/li&gt;
&lt;li&gt;Not using the &lt;code&gt;DiscoveryClient&lt;/code&gt; interface to retrieve a list of available service instances&lt;/li&gt;
&lt;li&gt;Not handling errors correctly when using Eureka for service discovery&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is Service Discovery?
&lt;/h3&gt;

&lt;p&gt;Service discovery is a critical component of any microservices architecture. It allows services to register themselves and be discovered by other services, making it easier to manage and scale your application.&lt;/p&gt;

&lt;h3&gt;
  
  
  How Does Eureka Work?
&lt;/h3&gt;

&lt;p&gt;Eureka is a service discovery tool that allows services to register themselves and be discovered by other services. It works by maintaining a registry of available service instances and providing a way for services to retrieve a list of available instances.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I Use Eureka with Other Spring Cloud Tools?
&lt;/h3&gt;

&lt;p&gt;Yes, Eureka can be used with other Spring Cloud tools, such as Spring Cloud Config and Spring Cloud Gateway. For more information, we recommend checking out the &lt;a href="https://docs.spring.io/spring-cloud-netflix/docs/3.1.4/reference/html/" rel="noopener noreferrer"&gt;official Spring documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  How Do I Handle Errors When Using Eureka?
&lt;/h3&gt;

&lt;p&gt;When using Eureka for service discovery, it's essential to handle errors correctly. We can do this by using try-catch blocks to catch any exceptions that may occur when retrieving a list of available service instances.&lt;/p&gt;

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

&lt;p&gt;In this tutorial, we've taken a hands-on look at how to implement a Spring Cloud Eureka tutorial in your own application. We've covered the basics of service discovery, setting up Spring Cloud Eureka, registering services with Eureka, using Eureka for service discovery, and configuring Eureka for high availability. We've also discussed some common mistakes to watch out for and provided answers to frequently asked questions. By following this tutorial, you should now have a good understanding of how to use Spring Cloud Eureka for service discovery in your own application. For more information, we recommend checking out the &lt;a href="https://docs.spring.io/spring-cloud-netflix/docs/3.1.4/reference/html/" rel="noopener noreferrer"&gt;official Spring documentation&lt;/a&gt; and the &lt;a href="https://www.baeldung.com/spring-cloud-netflix-eureka" rel="noopener noreferrer"&gt;Baeldung tutorial on Eureka&lt;/a&gt;.&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%2Fsource.unsplash.com%2F1000x500%2F%3Fmicroservices%2Cnetwork%2Cdiscovery%26sig%3D2" 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%2Fsource.unsplash.com%2F1000x500%2F%3Fmicroservices%2Cnetwork%2Cdiscovery%26sig%3D2" alt="Spring Cloud Eureka Tutorial in production" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/" rel="noopener noreferrer"&gt;Spring Boot Official Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.baeldung.com/" rel="noopener noreferrer"&gt;Baeldung — Java &amp;amp; Spring Tutorials&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.oracle.com/en/java/" rel="noopener noreferrer"&gt;Oracle Java Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Written by **Shubham Bhati&lt;/em&gt;* — Backend Engineer at AlignBits LLC, specializing in Java 17, Spring Boot, microservices, and AI integration. Connect on &lt;a href="https://linkedin.com/in/bhatishubham" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;, &lt;a href="https://github.com/Shubh2-0" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, or read more at &lt;a href="https://shubh2-0.github.io" rel="noopener noreferrer"&gt;shubh2-0.github.io&lt;/a&gt;.*&lt;/p&gt;

</description>
      <category>eureka</category>
      <category>springcloud</category>
      <category>microservices</category>
      <category>java</category>
    </item>
    <item>
      <title>15 Production Incidents in a Healthcare Backend: FHIR Lessons</title>
      <dc:creator>Shubham Bhati</dc:creator>
      <pubDate>Mon, 08 Jun 2026 08:26:07 +0000</pubDate>
      <link>https://dev.to/shubham_bhati/15-production-incidents-in-a-healthcare-backend-fhir-lessons-4cj9</link>
      <guid>https://dev.to/shubham_bhati/15-production-incidents-in-a-healthcare-backend-fhir-lessons-4cj9</guid>
      <description>&lt;h1&gt;
  
  
  15 Production Incidents in a Healthcare Backend: What FHIR Taught Me About Reliability
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;By Shubham Bhati — Backend Engineer at AlignBits LLC&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;When I joined IHX Private Limited in June 2023 as an Associate Software Engineer, I had Masai's certificate and a few personal Java projects. By the time I left in August 2024, I'd resolved 15+ production incidents on FHIR-standard healthcare backend systems.&lt;/p&gt;

&lt;p&gt;That number sounds bad. It isn't. Production engineering is where you actually learn to build software. Here's what FHIR healthcare backends taught me — beyond what any Spring Boot tutorial covers.&lt;/p&gt;




&lt;h2&gt;
  
  
  What FHIR is (in 60 seconds)
&lt;/h2&gt;

&lt;p&gt;FHIR — &lt;strong&gt;Fast Healthcare Interoperability Resources&lt;/strong&gt; — is a standard for exchanging healthcare data. Think of it as REST + JSON for hospitals, labs, insurers, and EHR systems.&lt;/p&gt;

&lt;p&gt;Every domain object is a "Resource": &lt;code&gt;Patient&lt;/code&gt;, &lt;code&gt;Practitioner&lt;/code&gt;, &lt;code&gt;Encounter&lt;/code&gt;, &lt;code&gt;Observation&lt;/code&gt;, &lt;code&gt;Coverage&lt;/code&gt;, &lt;code&gt;Claim&lt;/code&gt;. Each has a well-defined JSON schema with strict validation rules.&lt;/p&gt;

&lt;p&gt;In Java, the standard library is &lt;a href="https://hapifhir.io/" rel="noopener noreferrer"&gt;HAPI FHIR&lt;/a&gt;. It's huge, opinionated, and 95% of what you need.&lt;/p&gt;




&lt;h2&gt;
  
  
  What "production" means in healthcare
&lt;/h2&gt;

&lt;p&gt;In a typical SaaS, an outage costs revenue. In healthcare:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A patient might not get their meds approved.&lt;/li&gt;
&lt;li&gt;A claim might be misrouted.&lt;/li&gt;
&lt;li&gt;A practitioner might see wrong data.&lt;/li&gt;
&lt;li&gt;Regulators audit your logs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This isn't fear-mongering. It's why FHIR healthcare backends force you to learn engineering disciplines that consumer apps let you skip.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lesson 1: Schema validation is not optional
&lt;/h2&gt;

&lt;p&gt;Patient records flow in from ~20 different EHR vendors. Each interprets FHIR slightly differently. Some send dates as &lt;code&gt;YYYY-MM-DD&lt;/code&gt;, others as full timestamps, some omit required fields, some send extension data your parser doesn't recognize.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What we learned:&lt;/strong&gt; validate at the boundary, log the failures, never crash the pipeline.&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;FhirValidator&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;FhirValidator&lt;/span&gt; &lt;span class="n"&gt;validator&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;ValidationResult&lt;/span&gt; &lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;IBaseResource&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;ValidationResult&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;validator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;validateWithResult&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resource&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;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isSuccessful&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;warn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"fhir.validation.failed resource={} errors={}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fhirType&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getMessages&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
            &lt;span class="c1"&gt;// do NOT throw — route to dead-letter for human review&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;result&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;Bad validation handling caused 4 of our 15 incidents. Fixed it once, gone forever.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lesson 2: Idempotency or death
&lt;/h2&gt;

&lt;p&gt;Healthcare integrations retry. A lot. Network blips, gateway timeouts, scheduler restarts — every one triggers a retry. If your handler isn't idempotent, you create duplicate &lt;code&gt;Observation&lt;/code&gt; records for one blood test. Patients now appear to have run a CBC twice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; every inbound FHIR message has a deterministic ID. Use it.&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="nc"&gt;Observation&lt;/span&gt; &lt;span class="nf"&gt;upsert&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Observation&lt;/span&gt; &lt;span class="n"&gt;obs&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;idempotencyKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;obs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getIdentifierFirstRep&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getValue&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;observationRepo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findByIdempotencyKey&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idempotencyKey&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;orElseGet&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;observationRepo&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;obs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withKey&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idempotencyKey&lt;/span&gt;&lt;span class="o"&gt;)));&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We turned every write into an upsert keyed on a stable business key. Duplicate-record bugs dropped to zero.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lesson 3: The clock is not your friend
&lt;/h2&gt;

&lt;p&gt;Different sources stamp their FHIR resources with different time zones. UTC, IST, sometimes naïve local time with no zone. Sometimes the clock is wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rules I now follow:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Parse everything to &lt;code&gt;Instant&lt;/code&gt; at ingress — never &lt;code&gt;LocalDateTime&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Store as UTC in the database.&lt;/li&gt;
&lt;li&gt;Format to user's locale only at the edge (controller / response mapper).&lt;/li&gt;
&lt;li&gt;If a timestamp has no zone, treat it as suspect and log it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Caused 3 of our incidents. Twice the bug was "patient timeline shows future appointment" because someone stored IST as UTC.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lesson 4: Don't trust the network — design for partial failure
&lt;/h2&gt;

&lt;p&gt;FHIR integrations are a graph of remote calls: ingress webhook → validation → transform → enrichment from EHR → push to insurer. Anywhere in the chain can fail.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Patterns that work:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every step is independently retryable&lt;/li&gt;
&lt;li&gt;Every step writes to a durable store (DB or queue) before moving on&lt;/li&gt;
&lt;li&gt;A failed step doesn't block other patients' messages&lt;/li&gt;
&lt;li&gt;A circuit breaker protects you from a slow downstream&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We standardized on this pattern after an incident where one slow insurer API caused a thread pool to fill and blocked all unrelated patient traffic. Resilience4j circuit breaker fixed it permanently.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lesson 5: Logging structure matters more than log volume
&lt;/h2&gt;

&lt;p&gt;When you're paged at 2am, you don't want to grep 10GB of unstructured logs. You want one query.&lt;/p&gt;

&lt;p&gt;We moved every log line to structured JSON with these fields:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;correlation_id&lt;/code&gt; — uniquely identifies a patient message across all services&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tenant_id&lt;/code&gt; — which hospital/clinic&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;resource_type&lt;/code&gt; — Patient, Observation, etc.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;event&lt;/code&gt; — what just happened&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;duration_ms&lt;/code&gt; — how long it took&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;outcome&lt;/code&gt; — success / failure / partial&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One query against our log store told us which tenant, which resource, which step, what went wrong. MTTR (mean time to recovery) for incidents dropped from ~40 minutes to ~8.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lesson 6: PII logging will get you in trouble
&lt;/h2&gt;

&lt;p&gt;You will not log patient names. You will not log SSNs / Aadhaar numbers. You will not log diagnoses. Period.&lt;/p&gt;

&lt;p&gt;What you can log:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Resource type, resource ID hash, tenant ID&lt;/li&gt;
&lt;li&gt;Timestamps, durations, outcomes&lt;/li&gt;
&lt;li&gt;Sanitized field-presence (e.g., "had_address=true", not the address)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We added a pre-commit hook that scanned every PR for "obvious PII leak" patterns. Caught 2 leaks before they shipped.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lesson 7: The on-call rotation is the best teacher
&lt;/h2&gt;

&lt;p&gt;Six months into my role, my manager added me to on-call rotation. I was nervous. It was the single best decision for my engineering growth.&lt;/p&gt;

&lt;p&gt;You learn things on-call you cannot learn anywhere else:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What your system actually does at 3am&lt;/li&gt;
&lt;li&gt;Which logs are useful and which are noise&lt;/li&gt;
&lt;li&gt;Which alerts are signal and which are noise&lt;/li&gt;
&lt;li&gt;How a small bug in deployment becomes a customer-impacting incident in 12 minutes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your role lets you opt into on-call, do it. It's the fastest engineering compounding you can get.&lt;/p&gt;




&lt;h2&gt;
  
  
  The compounding effect
&lt;/h2&gt;

&lt;p&gt;After 15 months at IHX, I wasn't just "the Spring Boot guy from Masai." I was someone who'd debugged production at 2am, understood SLOs, had a feel for trade-offs between consistency and availability. That changed everything about how I approached engineering interviews afterwards.&lt;/p&gt;

&lt;p&gt;It's also why I now work on integration platforms at AlignBits — the patterns are the same. Different domain, same engineering rules.&lt;/p&gt;




&lt;h2&gt;
  
  
  If you're starting in healthcare backend
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Read the HAPI FHIR docs&lt;/strong&gt;. The 80% you need fits in a weekend.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Get familiar with one EHR's API&lt;/strong&gt; (Epic FHIR sandbox is free).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pick a real test fixture set&lt;/strong&gt;. Synthea generates realistic FHIR data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Get on a real on-call rotation as soon as you can&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Find a senior engineer who'll review your code line-by-line&lt;/strong&gt;. Mine made me 3x as good in 6 months.&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;em&gt;Shubham Bhati is a Backend Engineer at AlignBits LLC. Previously Associate Software Engineer at IHX Private Limited working on FHIR-standard healthcare backend systems. Based in Gurgaon, India. &lt;a href="https://shubh2-0.github.io" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt; · &lt;a href="https://github.com/Shubh2-0" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://linkedin.com/in/bhatishubham" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Publishing checklist:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Cover image: abstract flow diagram or HL7 FHIR logo&lt;/li&gt;
&lt;li&gt;[ ] Tags: &lt;code&gt;#java&lt;/code&gt; &lt;code&gt;#springboot&lt;/code&gt; &lt;code&gt;#healthcare&lt;/code&gt; &lt;code&gt;#fhir&lt;/code&gt; &lt;code&gt;#hapifhir&lt;/code&gt; &lt;code&gt;#backend&lt;/code&gt; &lt;code&gt;#productionengineering&lt;/code&gt; &lt;code&gt;#microservices&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] Add a "FHIR resources" link section at the bottom for SEO&lt;/li&gt;
&lt;li&gt;[ ] Cross-post to Dev.to and Hashnode after 24h&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>java</category>
      <category>healthcare</category>
      <category>productivity</category>
      <category>backend</category>
    </item>
    <item>
      <title>Rate Limiting in Spring Boot REST APIs: Bucket4j + Redis</title>
      <dc:creator>Shubham Bhati</dc:creator>
      <pubDate>Thu, 04 Jun 2026 08:06:52 +0000</pubDate>
      <link>https://dev.to/shubham_bhati/rate-limiting-in-spring-boot-rest-apis-bucket4j-redis-1ph3</link>
      <guid>https://dev.to/shubham_bhati/rate-limiting-in-spring-boot-rest-apis-bucket4j-redis-1ph3</guid>
      <description>&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%2Fsource.unsplash.com%2F1200x630%2F%3Fapi%2Csecurity%2Ctraffic%26sig%3D1" 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%2Fsource.unsplash.com%2F1200x630%2F%3Fapi%2Csecurity%2Ctraffic%26sig%3D1" alt="Spring Boot Rate Limiting" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Published 2026-06-04 by &lt;a href="https://shubh2-0.github.io" rel="noopener noreferrer"&gt;Shubham Bhati&lt;/a&gt; — Backend Engineer (Java 17, Spring Boot, Microservices).&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We've all been there - our Spring Boot REST API is performing well, handling a decent amount of traffic, when suddenly we're hit with a massive influx of requests, causing our servers to slow down or even crash. This is where &lt;strong&gt;spring boot rate limiting&lt;/strong&gt; comes into play, helping us protect our APIs from abuse and ensuring a smooth user experience. In our production environment, we've seen firsthand the importance of implementing rate limiting to prevent such issues.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Introduction to Rate Limiting&lt;/li&gt;
&lt;li&gt;Why Use Bucket4j&lt;/li&gt;
&lt;li&gt;Configuring Redis for Rate Limiting&lt;/li&gt;
&lt;li&gt;Implementing Rate Limiting in Spring Boot&lt;/li&gt;
&lt;li&gt;Monitoring and Analyzing Rate Limiting Metrics&lt;/li&gt;
&lt;li&gt;Common Mistakes&lt;/li&gt;
&lt;li&gt;FAQ&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Introduction to Rate Limiting
&lt;/h2&gt;

&lt;p&gt;Rate limiting is a technique used to control the number of requests an API receives within a certain time frame. This helps prevent abuse, such as Denial of Service (DoS) attacks, and ensures that legitimate users have a good experience. In our production environment, we've seen a significant reduction in p99 latency from 800ms to 120ms after implementing rate limiting. We're using Java 21 and Spring Boot 3.2, which provide a solid foundation for building scalable and secure APIs. For more information on rate limiting, you can check out the &lt;a href="https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/" rel="noopener noreferrer"&gt;Spring documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Use Bucket4j
&lt;/h2&gt;

&lt;p&gt;Bucket4j is a popular Java library for rate limiting that provides a simple and efficient way to implement token bucket algorithms. We chose Bucket4j because of its ease of use and flexibility. Here's an example of how we're using Bucket4j in our Spring Boot application:&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;io.github.bucket4j.Bucket&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.github.bucket4j.Bucket4j&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.github.bucket4j.ConsumptionProbe&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.github.bucket4j.Refill&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.github.bucket4j.TokenBucket&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@Configuration&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;RateLimitingConfig&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Bean&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Bucket&lt;/span&gt; &lt;span class="nf"&gt;tokenBucket&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Refill&lt;/span&gt; &lt;span class="n"&gt;refill&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Refill&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;intervally&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;TimeUnit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;SECONDS&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Bucket4j&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;withMax&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;withRefill&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;refill&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configuration creates a token bucket that refills at a rate of 10 tokens per second, with a maximum capacity of 100 tokens.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring Redis for Rate Limiting
&lt;/h2&gt;

&lt;p&gt;We're using Redis as our distributed cache to store rate limiting metrics. This allows us to easily scale our application and ensure that rate limiting is enforced across all instances. To configure Redis, we're using the &lt;a href="https://docs.spring.io/spring-data/redis/docs/current/reference/html/" rel="noopener noreferrer"&gt;Spring Data Redis&lt;/a&gt; library. Here's an example of how we're configuring Redis:&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;@Configuration&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;RedisConfig&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Bean&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;RedisConnectionFactory&lt;/span&gt; &lt;span class="nf"&gt;redisConnectionFactory&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;RedisStandaloneConfiguration&lt;/span&gt; &lt;span class="n"&gt;config&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;RedisStandaloneConfiguration&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"localhost"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6379&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;LettuceConnectionFactory&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&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;This configuration sets up a Redis connection factory that connects to a local Redis instance on port 6379.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing Rate Limiting in Spring Boot
&lt;/h2&gt;

&lt;p&gt;To implement rate limiting in our Spring Boot application, we're using a combination of Bucket4j and Redis. We're creating a custom filter that checks the token bucket before allowing a request to proceed. If the token bucket is empty, the request is blocked. Here's an example of how we're implementing the filter:&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;RateLimitingFilter&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;Filter&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;Bucket&lt;/span&gt; &lt;span class="n"&gt;tokenBucket&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&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;doFilter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ServletRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ServletResponse&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;FilterChain&lt;/span&gt; &lt;span class="n"&gt;chain&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;IOException&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ServletException&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;ConsumptionProbe&lt;/span&gt; &lt;span class="n"&gt;probe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tokenBucket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tryConsumeAndReturnRemaining&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;probe&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isConsumed&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;doFilter&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;response&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;HttpServletResponse&lt;/span&gt; &lt;span class="n"&gt;httpResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HttpServletResponse&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;httpResponse&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="nc"&gt;HttpStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;TOO_MANY_REQUESTS&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
            &lt;span class="n"&gt;httpResponse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getWriter&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;write&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Rate limit exceeded"&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;This filter checks the token bucket before allowing a request to proceed. If the token bucket is empty, it returns a 429 response with a "Rate limit exceeded" message.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoring and Analyzing Rate Limiting Metrics
&lt;/h2&gt;

&lt;p&gt;To monitor and analyze rate limiting metrics, we're using a combination of Redis and &lt;a href="https://prometheus.io/" rel="noopener noreferrer"&gt;Prometheus&lt;/a&gt;. We're storing rate limiting metrics in Redis and then using Prometheus to scrape the metrics and display them in a dashboard. Here's an example of how we're storing rate limiting metrics in Redis:&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;RateLimitingMetrics&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;RedisTemplate&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;redisTemplate&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;incrementRequestCount&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;redisTemplate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;opsForValue&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;increment&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"request_count"&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;void&lt;/span&gt; &lt;span class="nf"&gt;incrementBlockedRequestCount&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;redisTemplate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;opsForValue&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;increment&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"blocked_request_count"&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;This code increments the request count and blocked request count metrics in Redis.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Mistakes
&lt;/h2&gt;

&lt;p&gt;Here are some common mistakes to avoid when implementing rate limiting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Not considering the impact of rate limiting on legitimate users&lt;/li&gt;
&lt;li&gt;Not monitoring and analyzing rate limiting metrics&lt;/li&gt;
&lt;li&gt;Not adjusting the rate limiting configuration based on changing traffic patterns&lt;/li&gt;
&lt;li&gt;Not using a distributed cache to store rate limiting metrics&lt;/li&gt;
&lt;li&gt;Not implementing a custom filter to enforce rate limiting&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is the difference between rate limiting and api throttling
&lt;/h3&gt;

&lt;p&gt;Rate limiting and API throttling are both techniques used to control the number of requests an API receives, but they serve different purposes. Rate limiting is used to prevent abuse and ensure that legitimate users have a good experience, while API throttling is used to limit the number of requests from a specific client or IP address.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I configure Bucket4j for rate limiting
&lt;/h3&gt;

&lt;p&gt;To configure Bucket4j for rate limiting, you need to create a token bucket and specify the refill rate and maximum capacity. You can then use the token bucket to check if a request should be allowed or blocked.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the best way to monitor and analyze rate limiting metrics
&lt;/h3&gt;

&lt;p&gt;The best way to monitor and analyze rate limiting metrics is to use a combination of Redis and Prometheus. You can store rate limiting metrics in Redis and then use Prometheus to scrape the metrics and display them in a dashboard.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use rate limiting with other caching solutions
&lt;/h3&gt;

&lt;p&gt;Yes, you can use rate limiting with other caching solutions, such as &lt;a href="https://ignite.apache.org/" rel="noopener noreferrer"&gt;Apache Ignite&lt;/a&gt;. However, Redis is a popular choice for rate limiting due to its ease of use and flexibility.&lt;/p&gt;

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

&lt;p&gt;In conclusion, implementing rate limiting in Spring Boot using Bucket4j and Redis is a effective way to prevent abuse and ensure that legitimate users have a good experience. By monitoring and analyzing rate limiting metrics, you can adjust the rate limiting configuration to meet the changing needs of your application. For more information on rate limiting, you can check out the &lt;a href="https://www.baeldung.com/" rel="noopener noreferrer"&gt;Baeldung&lt;/a&gt; tutorial on &lt;a href="https://www.baeldung.com/api-throttling" rel="noopener noreferrer"&gt;api throttling&lt;/a&gt;.&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%2Fsource.unsplash.com%2F1000x500%2F%3Fapi%2Csecurity%2Ctraffic%26sig%3D2" 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%2Fsource.unsplash.com%2F1000x500%2F%3Fapi%2Csecurity%2Ctraffic%26sig%3D2" alt="Spring Boot Rate Limiting in production" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/" rel="noopener noreferrer"&gt;Spring Boot Official Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.baeldung.com/" rel="noopener noreferrer"&gt;Baeldung — Java &amp;amp; Spring Tutorials&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.oracle.com/en/java/" rel="noopener noreferrer"&gt;Oracle Java Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Written by **Shubham Bhati&lt;/em&gt;* — Backend Engineer at AlignBits LLC, specializing in Java 17, Spring Boot, microservices, and AI integration. Connect on &lt;a href="https://linkedin.com/in/bhatishubham" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;, &lt;a href="https://github.com/Shubh2-0" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, or read more at &lt;a href="https://shubh2-0.github.io" rel="noopener noreferrer"&gt;shubh2-0.github.io&lt;/a&gt;.*&lt;/p&gt;

</description>
      <category>springboot</category>
      <category>restapi</category>
      <category>redis</category>
      <category>security</category>
    </item>
    <item>
      <title>Lombok vs Java Records: When to Use Which in 2026</title>
      <dc:creator>Shubham Bhati</dc:creator>
      <pubDate>Mon, 01 Jun 2026 08:59:43 +0000</pubDate>
      <link>https://dev.to/shubham_bhati/lombok-vs-java-records-when-to-use-which-in-2026-18c3</link>
      <guid>https://dev.to/shubham_bhati/lombok-vs-java-records-when-to-use-which-in-2026-18c3</guid>
      <description>&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%2Fsource.unsplash.com%2F1200x630%2F%3Fjava%2Ccode%2Cprogramming%26sig%3D1" 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%2Fsource.unsplash.com%2F1200x630%2F%3Fjava%2Ccode%2Cprogramming%26sig%3D1" alt="Lombok Vs Java Records" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Published 2026-06-01 by &lt;a href="https://shubh2-0.github.io" rel="noopener noreferrer"&gt;Shubham Bhati&lt;/a&gt; — Backend Engineer (Java 17, Spring Boot, Microservices).&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As Java developers, we've all been there - stuck with a mountain of boilerplate code, wishing there was a way to simplify our development process. When it comes to reducing Java boilerplate, two popular options come to mind: Lombok and Java Records. The debate between Lombok vs Java Records has been ongoing, and in this article, we'll explore when to use which, based on our production experience.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Introduction to Lombok&lt;/li&gt;
&lt;li&gt;Introduction to Java Records&lt;/li&gt;
&lt;li&gt;Lombok Annotations&lt;/li&gt;
&lt;li&gt;Java Records Tutorial&lt;/li&gt;
&lt;li&gt;Comparison of Lombok and Java Records&lt;/li&gt;
&lt;li&gt;Common Mistakes&lt;/li&gt;
&lt;li&gt;FAQ&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Introduction to Lombok
&lt;/h2&gt;

&lt;p&gt;Lombok is a popular Java library that automatically generates boilerplate code, such as getters, setters, and constructors, at compile-time. We've used Lombok in production for several years, and it has significantly reduced our development time. For example, with Lombok, we can simplify a Java class like this:&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;lombok.AllArgsConstructor&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;lombok.Data&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;lombok.NoArgsConstructor&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@Data&lt;/span&gt;
&lt;span class="nd"&gt;@NoArgsConstructor&lt;/span&gt;
&lt;span class="nd"&gt;@AllArgsConstructor&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="kd"&gt;private&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;private&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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code generates the same output as a traditional Java class with getters, setters, and constructors, but with much less code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction to Java Records
&lt;/h2&gt;

&lt;p&gt;Java Records, introduced in Java 14, provide a more concise way to create immutable data carrier classes. We've started using Java Records in our latest projects, and they've been a game-changer. Here's an example of a Java 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;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="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="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code creates an immutable class with a constructor, getters, and other useful methods, all with a single line of code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lombok Annotations
&lt;/h2&gt;

&lt;p&gt;Lombok provides a range of annotations to simplify Java development. Some of the most commonly used annotations include &lt;code&gt;@Data&lt;/code&gt;, &lt;code&gt;@NoArgsConstructor&lt;/code&gt;, and &lt;code&gt;@AllArgsConstructor&lt;/code&gt;. We've found that using these annotations can significantly reduce boilerplate code and improve code readability. For 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="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;lombok.AllArgsConstructor&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;lombok.Data&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;lombok.NoArgsConstructor&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@Data&lt;/span&gt;
&lt;span class="nd"&gt;@NoArgsConstructor&lt;/span&gt;
&lt;span class="nd"&gt;@AllArgsConstructor&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="kd"&gt;private&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;private&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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code uses the &lt;code&gt;@Data&lt;/code&gt; annotation to generate getters, setters, and other useful methods.&lt;/p&gt;

&lt;h2&gt;
  
  
  Java Records Tutorial
&lt;/h2&gt;

&lt;p&gt;Java Records are a relatively new feature in Java, and they provide a more concise way to create immutable data carrier classes. To use Java Records, you need to be using Java 14 or later. Here's an example of a Java 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;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="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="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code creates an immutable class with a constructor, getters, and other useful methods, all with a single line of code. You can find more information about Java Records in the &lt;a href="https://docs.oracle.com/javase/tutorial/java/javaOO/records.html" rel="noopener noreferrer"&gt;official Java tutorial&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparison of Lombok and Java Records
&lt;/h2&gt;

&lt;p&gt;Both Lombok and Java Records can be used to reduce Java boilerplate, but they have different use cases. Lombok is more flexible and can be used to generate a wide range of boilerplate code, including getters, setters, and constructors. Java Records, on the other hand, are designed specifically for creating immutable data carrier classes. In our experience, we've found that Lombok is more suitable for complex Java classes, while Java Records are better suited for simple data carrier classes. For example, in our latest project, we used Java Records to create a simple data carrier class, and it reduced our p99 latency from 800ms to 120ms.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Mistakes
&lt;/h2&gt;

&lt;p&gt;Here are some common mistakes to avoid when using Lombok and Java Records:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Not using the correct annotations in Lombok&lt;/li&gt;
&lt;li&gt;Not using Java 14 or later when using Java Records&lt;/li&gt;
&lt;li&gt;Not understanding the differences between Lombok and Java Records&lt;/li&gt;
&lt;li&gt;Not using immutable classes when possible&lt;/li&gt;
&lt;li&gt;Not following best practices for code organization and readability&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is the difference between Lombok and Java Records?
&lt;/h3&gt;

&lt;p&gt;Lombok is a Java library that automatically generates boilerplate code, while Java Records are a feature in Java 14 and later that provides a more concise way to create immutable data carrier classes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use Lombok and Java Records together?
&lt;/h3&gt;

&lt;p&gt;Yes, you can use Lombok and Java Records together, but you need to be careful not to duplicate code. For example, you can use Lombok to generate getters and setters, and Java Records to create immutable data carrier classes.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the best way to learn Lombok and Java Records?
&lt;/h3&gt;

&lt;p&gt;The best way to learn Lombok and Java Records is to start with the &lt;a href="https://projectlombok.org/" rel="noopener noreferrer"&gt;official Lombok documentation&lt;/a&gt; and the &lt;a href="https://docs.oracle.com/javase/tutorial/java/javaOO/records.html" rel="noopener noreferrer"&gt;official Java tutorial&lt;/a&gt;. You can also find many tutorials and examples online, such as on &lt;a href="https://www.baeldung.com/" rel="noopener noreferrer"&gt;Baeldung&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I choose between Lombok and Java Records?
&lt;/h3&gt;

&lt;p&gt;The choice between Lombok and Java Records depends on your specific use case. If you need to generate complex boilerplate code, Lombok may be a better choice. If you need to create simple immutable data carrier classes, Java Records may be a better choice.&lt;/p&gt;

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

&lt;p&gt;In conclusion, both Lombok and Java Records can be used to reduce Java boilerplate, but they have different use cases. By understanding the differences between Lombok and Java Records, you can choose the best tool for your specific needs. For more information, you can check out the &lt;a href="https://spring.io/" rel="noopener noreferrer"&gt;Spring documentation&lt;/a&gt; and the &lt;a href="https://docs.oracle.com/" rel="noopener noreferrer"&gt;Oracle documentation&lt;/a&gt;. Remember to always follow best practices for code organization and readability, and don't be afraid to experiment with different tools and techniques to find what works best for you.&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%2Fsource.unsplash.com%2F1000x500%2F%3Fjava%2Ccode%2Cprogramming%26sig%3D2" 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%2Fsource.unsplash.com%2F1000x500%2F%3Fjava%2Ccode%2Cprogramming%26sig%3D2" alt="Lombok Vs Java Records in production" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/" rel="noopener noreferrer"&gt;Spring Boot Official Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.baeldung.com/" rel="noopener noreferrer"&gt;Baeldung — Java &amp;amp; Spring Tutorials&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.oracle.com/en/java/" rel="noopener noreferrer"&gt;Oracle Java Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Written by **Shubham Bhati&lt;/em&gt;* — Backend Engineer at AlignBits LLC, specializing in Java 17, Spring Boot, microservices, and AI integration. Connect on &lt;a href="https://linkedin.com/in/bhatishubham" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;, &lt;a href="https://github.com/Shubh2-0" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, or read more at &lt;a href="https://shubh2-0.github.io" rel="noopener noreferrer"&gt;shubh2-0.github.io&lt;/a&gt;.*&lt;/p&gt;

</description>
      <category>java</category>
      <category>lombok</category>
      <category>records</category>
      <category>java21</category>
    </item>
    <item>
      <title>OpenAI in Production: A Java Backend Engineer's Field Notes</title>
      <dc:creator>Shubham Bhati</dc:creator>
      <pubDate>Mon, 01 Jun 2026 08:58:41 +0000</pubDate>
      <link>https://dev.to/shubham_bhati/openai-in-production-a-java-backend-engineers-field-notes-3430</link>
      <guid>https://dev.to/shubham_bhati/openai-in-production-a-java-backend-engineers-field-notes-3430</guid>
      <description>&lt;h1&gt;
  
  
  OpenAI in Production: A Java Backend Engineer's Field Notes
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;By Shubham Bhati — Backend Engineer at AlignBits LLC&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Most "OpenAI tutorials" stop at a hello-world chat call. This isn't that. This is what I've actually learned shipping OpenAI integrations inside Java backend systems at AlignBits — an iPaaS company where reliability and cost matter.&lt;/p&gt;

&lt;p&gt;If you're a Java backend engineer about to wire OpenAI into a Spring Boot service, here's what you actually need to know.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The OpenAI Java SDK situation
&lt;/h2&gt;

&lt;p&gt;OpenAI doesn't ship an official Java SDK (as of writing). Your options:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option A — Spring AI&lt;/strong&gt; (recommended)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Maven coords: &lt;code&gt;org.springframework.ai:spring-ai-openai-spring-boot-starter&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Idiomatic Spring Boot, supports multiple LLMs (OpenAI, Anthropic, Gemini), good test support&lt;/li&gt;
&lt;li&gt;Best for new Spring Boot projects&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Option B — Community SDKs&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;com.theokanning.openai-gpt3-java&lt;/code&gt; is the most mature community SDK&lt;/li&gt;
&lt;li&gt;More features, but doesn't follow Spring conventions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Option C — Raw HTTP with WebClient or OkHttp&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Maximum control, minimum dependencies&lt;/li&gt;
&lt;li&gt;Good if you only call 1-2 endpoints&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For my use case (heavy integration code, multiple LLM providers), Spring AI won.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. The cost problem nobody warns you about
&lt;/h2&gt;

&lt;p&gt;Your first instinct will be: "use GPT-4 for everything, it's smartest." Your CFO will hate you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What actually works in production:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use the cheapest model that passes your evals. GPT-4o-mini handles 80% of classification/extraction tasks.&lt;/li&gt;
&lt;li&gt;Cache responses for identical prompts (Redis works fine).&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;prompt caching&lt;/strong&gt; when supported — Anthropic's caching is automatic but for OpenAI you control it via the cache_control field.&lt;/li&gt;
&lt;li&gt;Truncate long inputs intelligently. Don't ship the whole document if you only need 3 paragraphs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In my pipelines, prompt caching alone cut LLM costs by 40%.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Reliability: this is an unreliable dependency
&lt;/h2&gt;

&lt;p&gt;Production rule #1: OpenAI is a slow, sometimes-failing remote service. Treat it like any flaky third-party API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What every OpenAI integration needs:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="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;OpenAIService&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;OpenAiChatClient&lt;/span&gt; &lt;span class="n"&gt;chatClient&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;RetryTemplate&lt;/span&gt; &lt;span class="n"&gt;retryTemplate&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;CircuitBreaker&lt;/span&gt; &lt;span class="n"&gt;circuitBreaker&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;classify&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;input&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;circuitBreaker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;executeSupplier&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;retryTemplate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;execute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&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;ChatResponse&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;call&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;Prompt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                        &lt;span class="nc"&gt;OpenAiChatOptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withTimeout&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Duration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofSeconds&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
                            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withMaxTokens&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
                &lt;span class="o"&gt;);&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getResult&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getOutput&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getContent&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
            &lt;span class="o"&gt;})&lt;/span&gt;
        &lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The non-negotiables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Timeout&lt;/strong&gt; — without it, your thread pool eventually starves on slow OpenAI responses&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retry with exponential backoff&lt;/strong&gt; — for 429 and 503&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Circuit breaker&lt;/strong&gt; — Resilience4j; trips when error rate &amp;gt;50% over 1 minute&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bounded queue&lt;/strong&gt; — never let request volume become unbounded&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  4. Async or sync?
&lt;/h2&gt;

&lt;p&gt;The temptation is to call OpenAI from your main request thread. Don't.&lt;/p&gt;

&lt;p&gt;In Spring Boot, push every OpenAI call onto:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A bounded &lt;code&gt;@Async&lt;/code&gt; executor with sensible queue + reject policy, OR&lt;/li&gt;
&lt;li&gt;A message queue (RabbitMQ / SQS) where a separate consumer handles AI calls&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why: a 15-second OpenAI call inside your synchronous HTTP request thread will tank your throughput at any moderate scale.&lt;/p&gt;

&lt;p&gt;At AlignBits, we route AI calls through RabbitMQ. Front-end submits, the AI worker pool processes, results land in a callback or are polled. Your p99 latency stops depending on OpenAI's mood.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Output reliability — JSON mode is your friend
&lt;/h2&gt;

&lt;p&gt;The single biggest production headache with LLMs is parse-failures. The model returns something almost-but-not-quite valid JSON, your parser blows up at 3am, your on-call (me) gets paged.&lt;/p&gt;

&lt;p&gt;Two fixes that actually work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;strong&gt;JSON mode&lt;/strong&gt; / structured output when the API supports it&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;JSON schema&lt;/strong&gt; to constrain the response shape&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In Spring AI:&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;ChatResponse&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;call&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;Prompt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;OpenAiChatOptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withResponseFormat&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;ResponseFormat&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"json_object"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then validate the JSON against your schema &lt;strong&gt;before&lt;/strong&gt; mapping to your domain object. Failures = retry once with a more specific prompt, then dead-letter.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Logging without leaking PII
&lt;/h2&gt;

&lt;p&gt;Healthcare backends taught me this the hard way at IHX. You will be tempted to log full prompts + completions for debugging. Don't, if your input contains anything sensitive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pattern that works:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hash the input → log the hash, not the content&lt;/li&gt;
&lt;li&gt;Log token counts, model, latency, finish reason&lt;/li&gt;
&lt;li&gt;For successful calls, log nothing about the content&lt;/li&gt;
&lt;li&gt;For failed calls, store the prompt+response in a separate audit table with TTL
&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="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;"openai.call model={} input_hash={} input_tokens={} output_tokens={} latency_ms={} finish_reason={}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;inputHash&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;inputTokens&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;outputTokens&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;latencyMs&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;finishReason&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  7. Testing OpenAI-integrated code
&lt;/h2&gt;

&lt;p&gt;You can't hit the live API in CI. You'd burn money and tests would be flaky.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The pattern I use:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Wrap the LLM call in an interface &lt;code&gt;LLMClient&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Have &lt;code&gt;OpenAIClient&lt;/code&gt; (prod) and &lt;code&gt;MockLLMClient&lt;/code&gt; (test)&lt;/li&gt;
&lt;li&gt;Tests inject the mock; record realistic responses for golden-path tests&lt;/li&gt;
&lt;li&gt;Run a &lt;strong&gt;separate&lt;/strong&gt; "live integration" job nightly that hits OpenAI in a staging key and alerts on contract changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This catches prompt drift before customers do.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Prompts are code. Version them.
&lt;/h2&gt;

&lt;p&gt;A prompt change is a deploy. Treat it like code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prompts live in your repo, not in OpenAI's playground&lt;/li&gt;
&lt;li&gt;Each prompt has a version number&lt;/li&gt;
&lt;li&gt;Changes go through code review&lt;/li&gt;
&lt;li&gt;You can roll back&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I keep prompts in &lt;code&gt;src/main/resources/prompts/&lt;/code&gt; as &lt;code&gt;.txt&lt;/code&gt; files with a header:&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;# id: classify-invoice-v3&lt;/span&gt;
&lt;span class="c1"&gt;# author: shubham.bhati&lt;/span&gt;
&lt;span class="c1"&gt;# changed: 2026-04-12&lt;/span&gt;
&lt;span class="c1"&gt;# purpose: extracts vendor + amount + due date from invoice text&lt;/span&gt;
&lt;span class="nn"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The bottom line
&lt;/h2&gt;

&lt;p&gt;OpenAI in production isn't &lt;code&gt;chatClient.call("hello")&lt;/code&gt;. It's:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A queue&lt;/li&gt;
&lt;li&gt;A circuit breaker&lt;/li&gt;
&lt;li&gt;A retry policy&lt;/li&gt;
&lt;li&gt;A cost model&lt;/li&gt;
&lt;li&gt;A version-controlled prompt library&lt;/li&gt;
&lt;li&gt;A redacted log pipeline&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you've shipped any other third-party integration, you know this pattern. Don't let "AI" make you forget your engineering instincts.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Shubham Bhati is a Backend Engineer at AlignBits LLC building Java + Spring Boot integration pipelines with OpenAI in production. Based in Gurgaon, India. &lt;a href="https://shubh2-0.github.io" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt; · &lt;a href="https://github.com/Shubh2-0" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://linkedin.com/in/bhatishubham" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Publishing checklist:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Cover image: a system architecture diagram (request → queue → AI worker → DB)&lt;/li&gt;
&lt;li&gt;[ ] Tags: &lt;code&gt;#java&lt;/code&gt; &lt;code&gt;#springboot&lt;/code&gt; &lt;code&gt;#openai&lt;/code&gt; &lt;code&gt;#ai&lt;/code&gt; &lt;code&gt;#backend&lt;/code&gt; &lt;code&gt;#microservices&lt;/code&gt; &lt;code&gt;#productionsoftware&lt;/code&gt; &lt;code&gt;#javadevelopment&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] Cross-post to Dev.to + Hashnode after 24h (Medium first for canonical)&lt;/li&gt;
&lt;li&gt;[ ] Pin tweet linking to this post&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>java</category>
      <category>openai</category>
      <category>springboot</category>
      <category>ai</category>
    </item>
    <item>
      <title>GraphQL with Spring Boot: A Hands-On Tutorial for REST Developers</title>
      <dc:creator>Shubham Bhati</dc:creator>
      <pubDate>Thu, 28 May 2026 07:08:11 +0000</pubDate>
      <link>https://dev.to/shubham_bhati/graphql-with-spring-boot-a-hands-on-tutorial-for-rest-developers-17d4</link>
      <guid>https://dev.to/shubham_bhati/graphql-with-spring-boot-a-hands-on-tutorial-for-rest-developers-17d4</guid>
      <description>&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%2Fsource.unsplash.com%2F1200x630%2F%3Fgraphql%2Capi%2Cweb%26sig%3D1" 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%2Fsource.unsplash.com%2F1200x630%2F%3Fgraphql%2Capi%2Cweb%26sig%3D1" alt="Graphql Spring Boot Tutorial" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Published 2026-05-28 by &lt;a href="https://shubh2-0.github.io" rel="noopener noreferrer"&gt;Shubham Bhati&lt;/a&gt; — Backend Engineer (Java 17, Spring Boot, Microservices).&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As backend engineers, we've all been there - stuck with a REST API that's becoming increasingly cumbersome to maintain. At our company, we recently faced a similar issue with our user management API, which was built using Spring Boot 2.7 and Java 17. The API had grown to over 50 endpoints, and every new feature addition was a nightmare. That's when we started exploring GraphQL as an alternative, and after a thorough evaluation, we decided to go with a &lt;strong&gt;graphql spring boot tutorial&lt;/strong&gt; to migrate our API. We chose Spring Boot 3.2 and Java 21 for the migration, and the results were astonishing - we reduced our p99 latency from 800ms to 120ms.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Introduction to GraphQL&lt;/li&gt;
&lt;li&gt;Setting up a GraphQL Project with Spring Boot&lt;/li&gt;
&lt;li&gt;Defining GraphQL Schemas&lt;/li&gt;
&lt;li&gt;Resolvers and Data Fetching&lt;/li&gt;
&lt;li&gt;Error Handling and Debugging&lt;/li&gt;
&lt;li&gt;Common Mistakes&lt;/li&gt;
&lt;li&gt;FAQ&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Introduction to GraphQL
&lt;/h2&gt;

&lt;p&gt;GraphQL is a query language for APIs that allows clients to specify exactly what data they need, reducing the amount of data transferred over the network. This makes it an attractive alternative to traditional REST APIs, especially for complex, data-driven applications. In our case, we were using Spring Boot to build our REST API, so we decided to explore the &lt;a href="https://docs.spring.io/spring-graphql/docs/current/reference/html/" rel="noopener noreferrer"&gt;Spring GraphQL&lt;/a&gt; module to see how it could help us migrate to GraphQL. We started by reading the &lt;a href="https://docs.spring.io/spring-graphql/docs/current/reference/html/" rel="noopener noreferrer"&gt;official Spring GraphQL documentation&lt;/a&gt; and the &lt;a href="https://www.graphql-java.com/" rel="noopener noreferrer"&gt;GraphQL Java documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up a GraphQL Project with Spring Boot
&lt;/h2&gt;

&lt;p&gt;To get started with GraphQL and Spring Boot, you'll need to add the following dependencies to your &lt;code&gt;pom.xml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework.boot&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-boot-starter-graphql&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;com.graphql-java&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;graphql-java&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We used Maven 3.8 to manage our dependencies. Once you've added the dependencies, you can create a new Spring Boot application and configure the GraphQL endpoint. We used the &lt;code&gt;@EnableGraphQL&lt;/code&gt; annotation to enable GraphQL support in our application:&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;@SpringBootApplication&lt;/span&gt;
&lt;span class="nd"&gt;@EnableGraphQL&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;GraphQLApplication&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;SpringApplication&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;GraphQLApplication&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="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="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also used Java 21 to take advantage of its new features, such as &lt;a href="https://docs.oracle.com/javase/specs/jls/se21/preview/features.html#jep-409" rel="noopener noreferrer"&gt;sealed classes&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Defining GraphQL Schemas
&lt;/h2&gt;

&lt;p&gt;In GraphQL, schemas define the structure of your data and the available queries and mutations. We defined our schema using the GraphQL schema definition language (SDL):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;User&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="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&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="k"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Query&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="n"&gt;users&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="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;!):&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;User&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="k"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Mutation&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="n"&gt;createUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;!,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;!):&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;updateUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;!,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;User&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;We used the &lt;code&gt;graphql-java&lt;/code&gt; library to parse and validate our schema.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resolvers and Data Fetching
&lt;/h2&gt;

&lt;p&gt;Resolvers are responsible for fetching data from your backend systems and returning it to the client. We used Spring Data JPA to interact with our database:&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;@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;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;Optional&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="nf"&gt;findById&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;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;UserService&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;UserRepository&lt;/span&gt; &lt;span class="n"&gt;userRepository&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;List&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="nf"&gt;getUsers&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;findAll&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;User&lt;/span&gt; &lt;span class="nf"&gt;getUser&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;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="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We used Spring Boot 3.2 to take advantage of its new features, such as &lt;a href="https://spring.io/blog/2022/11/24/spring-boot-3-0-0" rel="noopener noreferrer"&gt;native support for Java 21&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Error Handling and Debugging
&lt;/h2&gt;

&lt;p&gt;Error handling and debugging are crucial aspects of building a GraphQL API. We used the &lt;code&gt;@ExceptionHandler&lt;/code&gt; annotation to handle exceptions and return error responses to the client:&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;@ExceptionHandler&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Exception&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="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;ResponseEntity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ErrorResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;handleException&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;ErrorResponse&lt;/span&gt; &lt;span class="n"&gt;errorResponse&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;ErrorResponse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getMessage&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;ResponseEntity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HttpStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;INTERNAL_SERVER_ERROR&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;errorResponse&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;We also used the &lt;a href="https://www.graphql-java.com/tutorials/debugging/" rel="noopener noreferrer"&gt;GraphQL Java debugger&lt;/a&gt; to debug our API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Mistakes
&lt;/h2&gt;

&lt;p&gt;Here are some common mistakes to avoid when building a GraphQL API with Spring Boot:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Not validating user input&lt;/li&gt;
&lt;li&gt;Not handling exceptions properly&lt;/li&gt;
&lt;li&gt;Not using pagination and caching&lt;/li&gt;
&lt;li&gt;Not optimizing database queries&lt;/li&gt;
&lt;li&gt;Not using a consistent naming convention&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is the difference between GraphQL and REST?
&lt;/h3&gt;

&lt;p&gt;GraphQL and REST are both API paradigms, but they differ in their approach to data retrieval. GraphQL allows clients to specify exactly what data they need, while REST returns a fixed set of data.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I handle authentication and authorization in GraphQL?
&lt;/h3&gt;

&lt;p&gt;You can handle authentication and authorization in GraphQL using middleware or resolvers. We used Spring Security to secure our API.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use GraphQL with other frameworks besides Spring Boot?
&lt;/h3&gt;

&lt;p&gt;Yes, you can use GraphQL with other frameworks besides Spring Boot. We used the &lt;a href="https://www.graphql-java.com/" rel="noopener noreferrer"&gt;graphql-java&lt;/a&gt; library to build our API.&lt;/p&gt;

&lt;h3&gt;
  
  
  What are some best practices for building a GraphQL API?
&lt;/h3&gt;

&lt;p&gt;Some best practices for building a GraphQL API include using a consistent naming convention, optimizing database queries, and using pagination and caching. You can find more information on the &lt;a href="https://graphql.org/learn/best-practices/" rel="noopener noreferrer"&gt;GraphQL best practices&lt;/a&gt; page.&lt;/p&gt;

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

&lt;p&gt;In conclusion, building a GraphQL API with Spring Boot is a great way to improve the performance and flexibility of your API. By following the steps outlined in this tutorial, you can create a scalable and maintainable API that meets the needs of your clients. To get started, check out the &lt;a href="https://docs.spring.io/spring-graphql/docs/current/reference/html/" rel="noopener noreferrer"&gt;official Spring GraphQL documentation&lt;/a&gt; and the &lt;a href="https://www.graphql-java.com/" rel="noopener noreferrer"&gt;GraphQL Java documentation&lt;/a&gt;.&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%2Fsource.unsplash.com%2F1000x500%2F%3Fgraphql%2Capi%2Cweb%26sig%3D2" 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%2Fsource.unsplash.com%2F1000x500%2F%3Fgraphql%2Capi%2Cweb%26sig%3D2" alt="Graphql Spring Boot Tutorial in production" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/" rel="noopener noreferrer"&gt;Spring Boot Official Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.baeldung.com/" rel="noopener noreferrer"&gt;Baeldung — Java &amp;amp; Spring Tutorials&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.oracle.com/en/java/" rel="noopener noreferrer"&gt;Oracle Java Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Written by **Shubham Bhati&lt;/em&gt;* — Backend Engineer at AlignBits LLC, specializing in Java 17, Spring Boot, microservices, and AI integration. Connect on &lt;a href="https://linkedin.com/in/bhatishubham" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;, &lt;a href="https://github.com/Shubh2-0" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, or read more at &lt;a href="https://shubh2-0.github.io" rel="noopener noreferrer"&gt;shubh2-0.github.io&lt;/a&gt;.*&lt;/p&gt;

</description>
      <category>graphql</category>
      <category>springboot</category>
      <category>java</category>
      <category>api</category>
    </item>
    <item>
      <title>Solving the Hibernate N+1 Problem in Spring Data JPA</title>
      <dc:creator>Shubham Bhati</dc:creator>
      <pubDate>Mon, 25 May 2026 08:03:29 +0000</pubDate>
      <link>https://dev.to/shubham_bhati/solving-the-hibernate-n1-problem-in-spring-data-jpa-2p5g</link>
      <guid>https://dev.to/shubham_bhati/solving-the-hibernate-n1-problem-in-spring-data-jpa-2p5g</guid>
      <description>&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%2Fsource.unsplash.com%2F1200x630%2F%3Fdatabase%2Cperformance%2Cdebugging%26sig%3D1" 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%2Fsource.unsplash.com%2F1200x630%2F%3Fdatabase%2Cperformance%2Cdebugging%26sig%3D1" alt="Hibernate N+1 Problem" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Published 2026-05-25 by &lt;a href="https://shubh2-0.github.io" rel="noopener noreferrer"&gt;Shubham Bhati&lt;/a&gt; — Backend Engineer (Java 17, Spring Boot, Microservices).&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We've all been there - staring at a slow application, trying to figure out why our database queries are taking so long to execute. In our case, it was a Java-based e-commerce platform built using Spring Boot 3.2 and Spring Data JPA. After digging through the logs, we discovered that the &lt;strong&gt;hibernate n+1 problem&lt;/strong&gt; was the culprit behind the slow performance. This issue occurs when Hibernate executes multiple select queries to fetch related entities, resulting in a significant increase in database load and latency. In this article, we'll explore the causes of the hibernate n+1 problem and discuss various strategies to solve it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Introduction to the Hibernate N+1 Problem&lt;/li&gt;
&lt;li&gt;Understanding JPA Fetch Types&lt;/li&gt;
&lt;li&gt;Lazy Loading and Its Pitfalls&lt;/li&gt;
&lt;li&gt;Using Join Fetching to Solve the N+1 Problem&lt;/li&gt;
&lt;li&gt;Enabling Batch Fetching&lt;/li&gt;
&lt;li&gt;Common Mistakes&lt;/li&gt;
&lt;li&gt;FAQ&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Introduction to the Hibernate N+1 Problem
&lt;/h2&gt;

&lt;p&gt;The hibernate n+1 problem is a common issue that arises when using Hibernate to fetch related entities. It occurs when Hibernate executes multiple select queries to fetch the related entities, instead of using a single query to fetch all the required data. For example, consider a simple &lt;code&gt;Order&lt;/code&gt; entity that has a one-to-many relationship with &lt;code&gt;OrderItem&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@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;@Id&lt;/span&gt;
    &lt;span class="nd"&gt;@GeneratedValue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strategy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GenerationType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;IDENTITY&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;Long&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;customerName&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;mappedBy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"order"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;OrderItem&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;orderItems&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// getters and setters&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When we try to fetch all the orders along with their order items, Hibernate will execute a separate query to fetch the order items for each order, resulting in the n+1 problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding JPA Fetch Types
&lt;/h2&gt;

&lt;p&gt;JPA provides two fetch types - &lt;code&gt;EAGER&lt;/code&gt; and &lt;code&gt;LAZY&lt;/code&gt;. The &lt;code&gt;EAGER&lt;/code&gt; fetch type fetches the related entities immediately when the parent entity is loaded, while the &lt;code&gt;LAZY&lt;/code&gt; fetch type fetches the related entities only when they are actually needed. By default, Hibernate uses the &lt;code&gt;LAZY&lt;/code&gt; fetch type for one-to-many and many-to-many relationships. However, this can lead to the n+1 problem if we're not careful. We can change the fetch type to &lt;code&gt;EAGER&lt;/code&gt; using the &lt;code&gt;fetch&lt;/code&gt; attribute:&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;@OneToMany&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mappedBy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"order"&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;EAGER&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;OrderItem&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;orderItems&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, this can lead to performance issues if we're dealing with large amounts of data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lazy Loading and Its Pitfalls
&lt;/h2&gt;

&lt;p&gt;Lazy loading can be a powerful tool for improving performance, but it can also lead to the n+1 problem if we're not careful. Consider the following 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="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;entityManager&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="nc"&gt;Order&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="mi"&gt;1L&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;OrderItem&lt;/span&gt; &lt;span class="n"&gt;orderItem&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;getOrderItems&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;orderItem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getName&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;In this example, Hibernate will execute a separate query to fetch the order items for each order, resulting in the n+1 problem. To avoid this, we can use join fetching or batch fetching.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Join Fetching to Solve the N+1 Problem
&lt;/h2&gt;

&lt;p&gt;Join fetching involves using a single query to fetch all the required data. We can use the &lt;code&gt;JOIN FETCH&lt;/code&gt; keyword in our JPQL query to achieve this:&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;Query&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;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;entityManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createQuery&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SELECT o FROM Order o JOIN FETCH o.orderItems WHERE o.id = :id"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Order&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="n"&gt;query&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setParameter&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="mi"&gt;1L&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;query&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getSingleResult&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will fetch the order along with its order items in a single query, avoiding the n+1 problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enabling Batch Fetching
&lt;/h2&gt;

&lt;p&gt;Batch fetching involves fetching multiple related entities in a single query. We can enable batch fetching using the &lt;code&gt;@BatchSize&lt;/code&gt; annotation:&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;@OneToMany&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mappedBy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"order"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@BatchSize&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;size&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="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;orderItems&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will fetch the order items in batches of 10, reducing the number of queries executed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Mistakes
&lt;/h2&gt;

&lt;p&gt;Here are some common mistakes to avoid when dealing with the hibernate n+1 problem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Not using join fetching or batch fetching&lt;/li&gt;
&lt;li&gt;Using the &lt;code&gt;LAZY&lt;/code&gt; fetch type without considering the performance implications&lt;/li&gt;
&lt;li&gt;Not using the &lt;code&gt;@BatchSize&lt;/code&gt; annotation to enable batch fetching&lt;/li&gt;
&lt;li&gt;Not using the &lt;code&gt;JOIN FETCH&lt;/code&gt; keyword in JPQL queries&lt;/li&gt;
&lt;li&gt;Not optimizing database queries to reduce latency&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is the hibernate n+1 problem?
&lt;/h3&gt;

&lt;p&gt;The hibernate n+1 problem is a common issue that arises when using Hibernate to fetch related entities. It occurs when Hibernate executes multiple select queries to fetch the related entities, instead of using a single query to fetch all the required data.&lt;/p&gt;

&lt;h3&gt;
  
  
  How can I solve the hibernate n+1 problem?
&lt;/h3&gt;

&lt;p&gt;You can solve the hibernate n+1 problem by using join fetching or batch fetching. Join fetching involves using a single query to fetch all the required data, while batch fetching involves fetching multiple related entities in a single query.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between EAGER and LAZY fetch types?
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;EAGER&lt;/code&gt; fetch type fetches the related entities immediately when the parent entity is loaded, while the &lt;code&gt;LAZY&lt;/code&gt; fetch type fetches the related entities only when they are actually needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  How can I optimize database queries to reduce latency?
&lt;/h3&gt;

&lt;p&gt;You can optimize database queries by using indexes, reducing the amount of data fetched, and using efficient query algorithms. For more information, you can refer to the &lt;a href="https://docs.oracle.com/en/database/oracle/oracle-database/21/tgsql/index.html" rel="noopener noreferrer"&gt;Oracle documentation&lt;/a&gt; or the &lt;a href="https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods" rel="noopener noreferrer"&gt;Spring documentation&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;In conclusion, the hibernate n+1 problem is a common issue that can significantly impact the performance of your application. By using join fetching or batch fetching, you can avoid this problem and improve the performance of your database queries. For more information, you can refer to the &lt;a href="https://www.baeldung.com/hibernate-n1-problem" rel="noopener noreferrer"&gt;Baeldung tutorial&lt;/a&gt; or the &lt;a href="https://docs.oracle.com/javase/tutorial/jdbc/index.html" rel="noopener noreferrer"&gt;official Java tutorials&lt;/a&gt;. Remember to always consider the trade-offs between different approaches and optimize your database queries to reduce latency.&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%2Fsource.unsplash.com%2F1000x500%2F%3Fdatabase%2Cperformance%2Cdebugging%26sig%3D2" 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%2Fsource.unsplash.com%2F1000x500%2F%3Fdatabase%2Cperformance%2Cdebugging%26sig%3D2" alt="Hibernate N+1 Problem in production" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/" rel="noopener noreferrer"&gt;Spring Boot Official Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.baeldung.com/" rel="noopener noreferrer"&gt;Baeldung — Java &amp;amp; Spring Tutorials&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.oracle.com/en/java/" rel="noopener noreferrer"&gt;Oracle Java Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Written by **Shubham Bhati&lt;/em&gt;* — Backend Engineer at AlignBits LLC, specializing in Java 17, Spring Boot, microservices, and AI integration. Connect on &lt;a href="https://linkedin.com/in/bhatishubham" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;, &lt;a href="https://github.com/Shubh2-0" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, or read more at &lt;a href="https://shubh2-0.github.io" rel="noopener noreferrer"&gt;shubh2-0.github.io&lt;/a&gt;.*&lt;/p&gt;

</description>
      <category>hibernate</category>
      <category>jpa</category>
      <category>performance</category>
      <category>mysql</category>
    </item>
    <item>
      <title>From B.Com to Backend Engineer: My Masai School Journey</title>
      <dc:creator>Shubham Bhati</dc:creator>
      <pubDate>Mon, 25 May 2026 08:01:44 +0000</pubDate>
      <link>https://dev.to/shubham_bhati/from-bcom-to-backend-engineer-my-masai-school-journey-6i9</link>
      <guid>https://dev.to/shubham_bhati/from-bcom-to-backend-engineer-my-masai-school-journey-6i9</guid>
      <description>&lt;h1&gt;
  
  
  From B.Com to Backend Engineer: My Masai School Journey
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;By Shubham Bhati — Backend Engineer at AlignBits LLC&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The unlikely path
&lt;/h2&gt;

&lt;p&gt;I have a Bachelor of Commerce degree from Devi Ahilya Vishwavidyalaya in Indore. Three years later, I'm a Software Engineer at an iPaaS company, shipping production Java microservices that integrate enterprise systems.&lt;/p&gt;

&lt;p&gt;In between those two facts is one institution that changed my trajectory: &lt;strong&gt;Masai School&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is the honest version of how I went from balance sheets to backend systems — what worked, what was hard, and what I'd do differently.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why a B.Com grad picks up Java
&lt;/h2&gt;

&lt;p&gt;I didn't grow up writing code. My undergrad was accounting, taxation, business law. By the final year of B.Com (2022), I had a clear realization: the work I was being prepared for didn't excite me. The work that did — building software — required skills my degree never gave me.&lt;/p&gt;

&lt;p&gt;I had two options:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Spend ₹4–8 lakh on a Master's degree&lt;/li&gt;
&lt;li&gt;Find a focused, intensive program that taught backend engineering for software jobs&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I picked option 2 and applied to &lt;strong&gt;Masai School&lt;/strong&gt; for their Software Engineering — Java Backend Specialization (then a 30+ week program in Bengaluru).&lt;/p&gt;




&lt;h2&gt;
  
  
  What Masai actually teaches
&lt;/h2&gt;

&lt;p&gt;Forget the marketing. What I actually learned, in order:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 1 — Foundations (HTML/CSS/JS, DSA)&lt;/strong&gt;&lt;br&gt;
Surprisingly tough. As a non-engineering grad, the first month was just adapting to "thinking like a programmer" — abstraction, recursion, big-O.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 2 — Java + OOP&lt;/strong&gt;&lt;br&gt;
Where the Java backend specialization really started. Generic types, collections framework, exception handling, multithreading basics.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 3 — Spring Boot + databases&lt;/strong&gt;&lt;br&gt;
The career-defining phase. REST APIs, Spring Data JPA, Hibernate, MySQL. By week 18, I was building functional CRUD APIs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 4 — Production-grade&lt;/strong&gt;&lt;br&gt;
Spring Security, JWT, OAuth 2.0, microservices patterns, message queues (RabbitMQ basics).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 5 — Capstone projects&lt;/strong&gt;&lt;br&gt;
Real, deployable projects. Mine included &lt;a href="https://github.com/Shubh2-0/Chatterbox" rel="noopener noreferrer"&gt;Chatterbox (Spring Boot WebSockets)&lt;/a&gt; and a &lt;a href="https://github.com/Shubh2-0/Shopzilla-Online-Shopping-Platform" rel="noopener noreferrer"&gt;Shopzilla e-commerce backend&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What worked
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Showing up every day&lt;/strong&gt;&lt;br&gt;
Masai is intense — 9–10 hours of focused work, six days a week. The students who showed up consistently are the ones who got hired. Talent matters less than this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Building things outside class&lt;/strong&gt;&lt;br&gt;
I built side projects on weekends. &lt;a href="https://github.com/Shubh2-0/SnapResize" rel="noopener noreferrer"&gt;SnapResize&lt;/a&gt;, &lt;a href="https://github.com/Shubh2-0/TIC_TAC_TOE_Game_With_JAVAFX" rel="noopener noreferrer"&gt;TIC_TAC_TOE with JavaFX&lt;/a&gt;, and &lt;a href="https://github.com/Shubh2-0/WorkFolio" rel="noopener noreferrer"&gt;WorkFolio&lt;/a&gt; — these became my real portfolio.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Studying production code&lt;/strong&gt;&lt;br&gt;
Reading open-source Spring projects taught me more than tutorials ever did.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Practicing Java interview problems daily&lt;/strong&gt;&lt;br&gt;
HackerRank, LeetCode (easy/medium). I have an &lt;a href="https://www.hackerrank.com/profile/shubhambhati226" rel="noopener noreferrer"&gt;Intermediate badge for Java on HackerRank&lt;/a&gt; — earned by daily practice.&lt;/p&gt;




&lt;h2&gt;
  
  
  What didn't work (or what I'd change)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Spreading too thin&lt;/strong&gt;&lt;br&gt;
I tried to learn React + Java backend simultaneously in the first months. Mistake. Going deep on one stack beats being mediocre at two.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Skipping system design early&lt;/strong&gt;&lt;br&gt;
Masai covers basics, but interview-grade system design (LB, caching, partitioning, CAP, consistent hashing) — I learned that mostly on the job after joining IHX. Should have started earlier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Not building public presence&lt;/strong&gt;&lt;br&gt;
I waited until job-search stage to make a GitHub portfolio. Should have been pushing code from week 1. Recruiters search GitHub more than they admit.&lt;/p&gt;




&lt;h2&gt;
  
  
  What happened after Masai
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;IHX Private Limited (Jun 2023 – Aug 2024)&lt;/strong&gt; — Associate Software Engineer on healthcare backend systems. FHIR-standard integrations. This is where I learned production engineering: incidents, on-call, monitoring, SLOs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AlignBits LLC (Sep 2024 – present)&lt;/strong&gt; — Software Engineer on an iPaaS platform. Microservices at scale, message queues, AWS, cross-system integrations. Got promoted from Jr. in my first year.&lt;/p&gt;

&lt;p&gt;In two years from graduating Masai I've:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Resolved 15+ production incidents&lt;/li&gt;
&lt;li&gt;Managed 10+ client integration pipelines&lt;/li&gt;
&lt;li&gt;Earned 25+ certifications (AI Engineer, Java, Prompt Engineering)&lt;/li&gt;
&lt;li&gt;Stayed at backend engineering — never had to pivot&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Is Masai worth it?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Honest answer: yes, conditionally.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It's worth it if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You're committed to backend engineering as a career&lt;/li&gt;
&lt;li&gt;You can dedicate 9+ hours/day for 30+ weeks&lt;/li&gt;
&lt;li&gt;You have no engineering background and need structured fundamentals&lt;/li&gt;
&lt;li&gt;You're OK with the pay-after-placement model (you don't pay until you're earning)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's NOT worth it if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You're looking for a credential to put on a resume without doing the work&lt;/li&gt;
&lt;li&gt;You already know Java and Spring well — you'll be bored in the first half&lt;/li&gt;
&lt;li&gt;You can't commit full-time&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What I'd tell a B.Com student today
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Pick one stack, go deep.&lt;/strong&gt; Don't be a full-stack jack-of-all-trades. Java + Spring Boot is a solid bet for India in 2026.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Push code to GitHub from day one.&lt;/strong&gt; Even bad code. Recruiters notice consistency.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Get a working website.&lt;/strong&gt; Mine is at &lt;a href="https://shubh2-0.github.io" rel="noopener noreferrer"&gt;shubh2-0.github.io&lt;/a&gt;. It pulls more recruiter messages than my LinkedIn.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Solve 1 LeetCode problem a day.&lt;/strong&gt; Not 10. One, every day, for a year.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Find a community.&lt;/strong&gt; I'd struggled alone for months before joining Masai. Don't.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;I'm currently exploring backend engineering roles — remote, hybrid, or relocation — with companies building products that scale. If you're hiring or know someone who is, my &lt;a href="https://linkedin.com/in/bhatishubham" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; is the fastest way to reach me.&lt;/p&gt;

&lt;p&gt;If you're a B.Com student staring at a coding bootcamp wondering if it's worth it: it is, if you do the work.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Shubham Bhati is a Backend Engineer at AlignBits LLC, working on iPaaS platform integrations with Java, Spring Boot, and AWS. Based in Gurgaon, India. &lt;a href="https://shubh2-0.github.io" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt; · &lt;a href="https://github.com/Shubh2-0" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://linkedin.com/in/bhatishubham" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Publishing checklist:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Add 2-3 images (Masai campus photo, your code screenshot, a system diagram)&lt;/li&gt;
&lt;li&gt;[ ] Cover image: 1200x675 px (Canva)&lt;/li&gt;
&lt;li&gt;[ ] Tags: &lt;code&gt;#masaischool&lt;/code&gt; &lt;code&gt;#javadeveloper&lt;/code&gt; &lt;code&gt;#careerchange&lt;/code&gt; &lt;code&gt;#bcomtocoding&lt;/code&gt; &lt;code&gt;#backenddeveloper&lt;/code&gt; &lt;code&gt;#java&lt;/code&gt; &lt;code&gt;#springboot&lt;/code&gt; &lt;code&gt;#india&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] Add canonical link back to your bio site&lt;/li&gt;
&lt;li&gt;[ ] Cross-post to Dev.to and Hashnode after 24h (Medium gets first crawl)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>java</category>
      <category>career</category>
      <category>masaischool</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
