<?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: HashCoder</title>
    <description>The latest articles on DEV Community by HashCoder (@hgm88x).</description>
    <link>https://dev.to/hgm88x</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2853595%2F35fe0029-a328-4d82-b65a-96a34c50ef35.png</url>
      <title>DEV Community: HashCoder</title>
      <link>https://dev.to/hgm88x</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hgm88x"/>
    <language>en</language>
    <item>
      <title>[Boost]</title>
      <dc:creator>HashCoder</dc:creator>
      <pubDate>Fri, 11 Jul 2025 10:00:48 +0000</pubDate>
      <link>https://dev.to/hgm88x/-7i4</link>
      <guid>https://dev.to/hgm88x/-7i4</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/hgm88x/from-zero-to-ai-hero-building-a-gemini-powered-chat-app-with-spring-boot-part-1-the-foundation-1ie0" class="crayons-story__hidden-navigation-link"&gt;From Zero to AI Hero: Building a Gemini-Powered Chat App with Spring Boot (Part 1: The Foundation)&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/hgm88x" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2853595%2F35fe0029-a328-4d82-b65a-96a34c50ef35.png" alt="hgm88x profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/hgm88x" class="crayons-story__secondary fw-medium m:hidden"&gt;
              HashCoder
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                HashCoder
                
              
              &lt;div id="story-author-preview-content-2676326" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/hgm88x" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2853595%2F35fe0029-a328-4d82-b65a-96a34c50ef35.png" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;HashCoder&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/hgm88x/from-zero-to-ai-hero-building-a-gemini-powered-chat-app-with-spring-boot-part-1-the-foundation-1ie0" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jul 10 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/hgm88x/from-zero-to-ai-hero-building-a-gemini-powered-chat-app-with-spring-boot-part-1-the-foundation-1ie0" id="article-link-2676326"&gt;
          From Zero to AI Hero: Building a Gemini-Powered Chat App with Spring Boot (Part 1: The Foundation)
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/java"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;java&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/spring"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;spring&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/genai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;genai&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/hgm88x/from-zero-to-ai-hero-building-a-gemini-powered-chat-app-with-spring-boot-part-1-the-foundation-1ie0" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;1&lt;span class="hidden s:inline"&gt; reaction&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/hgm88x/from-zero-to-ai-hero-building-a-gemini-powered-chat-app-with-spring-boot-part-1-the-foundation-1ie0#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            6 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>ai</category>
      <category>java</category>
      <category>spring</category>
      <category>genai</category>
    </item>
    <item>
      <title>From Zero to AI Hero: Building a Gemini-Powered Chat App with Spring Boot (Part 1: The Foundation)</title>
      <dc:creator>HashCoder</dc:creator>
      <pubDate>Thu, 10 Jul 2025 23:21:24 +0000</pubDate>
      <link>https://dev.to/hgm88x/from-zero-to-ai-hero-building-a-gemini-powered-chat-app-with-spring-boot-part-1-the-foundation-1ie0</link>
      <guid>https://dev.to/hgm88x/from-zero-to-ai-hero-building-a-gemini-powered-chat-app-with-spring-boot-part-1-the-foundation-1ie0</guid>
      <description>&lt;p&gt;The world of Large Language Models (LLMs) is moving at a breakneck pace, and as Spring developers, we have a first-class ticket to the show: &lt;strong&gt;Spring AI&lt;/strong&gt;. This incredible project from the Spring team demystifies the process of integrating powerful AI capabilities into our applications. It provides a unified, elegant abstraction over various AI providers, from OpenAI to Hugging Face, and, as we'll explore today, Google's powerful Gemini models.&lt;/p&gt;

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

&lt;p&gt;In this series, we'll build a complete, production-minded AI application. For Part 1, we're laying the essential groundwork. We will create a simple yet robust "Movie Expert" chatbot. This bot will leverage the Google Gemini Pro model to answer movie-related questions, and we'll build it with all the Spring Boot best practices you know and love: dependency injection, service layers, and even asynchronous processing.&lt;/p&gt;

&lt;p&gt;Ready to wire up your first AI? Let's get started.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Big Picture: Our Application Architecture
&lt;/h3&gt;

&lt;p&gt;Before we dive into the code, let's look at our application from 10,000 feet. The user flow is straightforward:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; A user types a question into a simple web UI.&lt;/li&gt;
&lt;li&gt; Our &lt;code&gt;ChatController&lt;/code&gt; receives the request.&lt;/li&gt;
&lt;li&gt; It passes the user's prompt to a dedicated &lt;code&gt;MovieChatService&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; The &lt;code&gt;MovieChatService&lt;/code&gt; constructs a carefully worded prompt (telling the AI its "persona") and sends it to the Gemini model via the Spring AI &lt;code&gt;ChatClient&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; Simultaneously, we'll asynchronously save the conversation to a PostgreSQL database for logging and future analysis using a &lt;code&gt;ChatLogService&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; The AI's response is sent back to the user's screen.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Simple, clean, and scalable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting the Stage: Configuration is Key
&lt;/h3&gt;

&lt;p&gt;The magic of Spring AI begins in your &lt;code&gt;application.properties&lt;/code&gt; (or &lt;code&gt;.yml&lt;/code&gt;) file. This is where you tell Spring how to connect to the AI model of your choice. Here’s how we configure our application to use Google Gemini.&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;application&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Spring AI Chat&lt;/span&gt;
  &lt;span class="c1"&gt;# --- Database Configuration ---&lt;/span&gt;
  &lt;span class="na"&gt;datasource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jdbc:postgresql://localhost:5432/moviedb&lt;/span&gt;
    &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;admin&lt;/span&gt;
    &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;secret&lt;/span&gt;
    &lt;span class="na"&gt;driver-class-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;org.postgresql.Driver&lt;/span&gt;
  &lt;span class="na"&gt;jpa&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;hibernate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;ddl-auto&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;create-drop&lt;/span&gt; &lt;span class="c1"&gt;# For demo purposes; use 'validate' or 'update' in prod&lt;/span&gt;
    &lt;span class="na"&gt;show-sql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;hibernate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;format_sql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgresql&lt;/span&gt;
    &lt;span class="na"&gt;database-platform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;org.hibernate.dialect.PostgreSQLDialect&lt;/span&gt;

  &lt;span class="c1"&gt;# --- Spring AI Configuration for Gemini ---&lt;/span&gt;
  &lt;span class="na"&gt;ai&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;openai&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;chat&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gemini-2.0-flash&lt;/span&gt; &lt;span class="c1"&gt;# Or gemini-1.5-flash, etc.&lt;/span&gt;
        &lt;span class="na"&gt;base-url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://generativelanguage.googleapis.com&lt;/span&gt;
        &lt;span class="c1"&gt;# This path is crucial for the OpenAI-compatible endpoint&lt;/span&gt;
        &lt;span class="na"&gt;completions-path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/v1beta/models/{model}:generateContent&lt;/span&gt; 
      &lt;span class="na"&gt;api-key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${GEMINI_API_KEY}&lt;/span&gt; &lt;span class="c1"&gt;# Best practice: use environment variables&lt;/span&gt;

  &lt;span class="c1"&gt;# --- Async Task Executor Configuration ---&lt;/span&gt;
  &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;execution&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;pool&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;core-size&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;max-size&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's break down the most important part—the &lt;code&gt;spring.ai&lt;/code&gt; block:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Wait, &lt;code&gt;openai&lt;/code&gt;? For Gemini?&lt;/strong&gt; Yes, and this is a brilliant piece of engineering. Google provides an OpenAI-compatible endpoint for Gemini. This means we can use the battle-tested Spring AI &lt;code&gt;openai-spring-boot-starter&lt;/code&gt; dependency and simply point it to Google's API. This gives us incredible flexibility without needing a separate, dedicated Gemini starter.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;model&lt;/code&gt;: Here, we specify which Gemini model we want to use. I'm using &lt;code&gt;gemini-2.0-flash&lt;/code&gt;, but you could easily switch to &lt;code&gt;gemini-1.5-flash&lt;/code&gt; or another variant.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;base-url&lt;/code&gt;: This is the root URL for Google's Generative Language API.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;completions-path&lt;/code&gt;: &lt;strong&gt;This is the critical line.&lt;/strong&gt; It tells Spring AI the exact path to use for generating content, which differs from the standard OpenAI path. &lt;em&gt;Note: The path in the code provided in the prompt was slightly off; the official path for the REST API is &lt;code&gt;/v1beta/models/{model}:generateContent&lt;/code&gt;.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;api-key&lt;/code&gt;: Your secret API key from the &lt;a href="https://aistudio.google.com/app/apikey" rel="noopener noreferrer"&gt;Google AI Studio&lt;/a&gt;. I strongly recommend loading this from an environment variable (&lt;code&gt;${GEMINI_API_KEY}&lt;/code&gt;) rather than hardcoding it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We've also configured our database and a thread pool for asynchronous tasks, which we'll see in action shortly.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Core Logic: Communicating with the AI
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;MovieChatService&lt;/code&gt; is the heart of our application. It's responsible for taking the user's raw input, giving it context, and interacting with the &lt;code&gt;ChatClient&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;@Slf4j&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;MovieChatService&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;ChatClient&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;ChatLogService&lt;/span&gt; &lt;span class="n"&gt;chatLogService&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Constructor injection - a Spring best practice&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;MovieChatService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChatClient&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ChatLogService&lt;/span&gt; &lt;span class="n"&gt;chatLogService&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;chatClient&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="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;chatLogService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chatLogService&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;getMovieChatResponse&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;userPrompt&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// 1. Define the AI's persona with a System Prompt&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;systemPromptTemplate&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;SystemPromptTemplate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Constants&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;MOVIE_PROMPT&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;systemMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;systemPromptTemplate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createMessage&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// 2. Create a prompt from the user's direct input&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;userPromptTemplate&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;PromptTemplate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userPrompt&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;userMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;userPromptTemplate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createMessage&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// 3. Combine prompts and create the final Prompt object&lt;/span&gt;
        &lt;span class="c1"&gt;// NOTE: A more idiomatic Spring AI way is to pass a list of Messages&lt;/span&gt;
        &lt;span class="nc"&gt;Prompt&lt;/span&gt; &lt;span class="n"&gt;prompt&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;Prompt&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;systemMessage&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;userMessage&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

        &lt;span class="c1"&gt;// 4. Call the AI model&lt;/span&gt;
        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;aiResponse&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;prompt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;)&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="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// 5. Asynchronously save the interaction&lt;/span&gt;
        &lt;span class="n"&gt;chatLogService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;saveLog&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userPrompt&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;aiResponse&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;aiResponse&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;Let's walk through this method:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;System Prompt:&lt;/strong&gt; We use a &lt;code&gt;SystemPromptTemplate&lt;/code&gt; to set the AI's persona. This is our "meta-instruction" that governs all its subsequent answers. We're telling it: "You are a helpful and knowledgeable movie expert." This is crucial for controlling the AI's tone and domain.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Constants.java&lt;/span&gt;
&lt;span class="kd"&gt;public&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;MOVIE_PROMPT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""
         You are a helpful and knowledgeable movie expert.
         Your main goal is to answer questions about movies, actors, directors, and film history.
         Please be concise and friendly in your responses.
         If the user asks a question that is NOT related to movies, politely decline to answer
         and remind them that you are a movie expert. Give only 1 fact at a time.
        """&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;User Prompt:&lt;/strong&gt; We wrap the user's raw string in a &lt;code&gt;PromptTemplate&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Combine and Call:&lt;/strong&gt; The modern Spring AI approach is to create a &lt;code&gt;Prompt&lt;/code&gt; object containing a list of &lt;code&gt;Message&lt;/code&gt; objects (in this case, our system and user messages). This structured format is what the &lt;code&gt;ChatClient&lt;/code&gt; expects.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Magic Call:&lt;/strong&gt; The line &lt;code&gt;chatClient.prompt(prompt).call().content()&lt;/code&gt; is the beautiful abstraction Spring AI provides. Under the hood, it's creating an HTTP request to the Gemini API, including your API key, formatting the JSON payload, sending the request, and parsing the response to give you back a simple &lt;code&gt;String&lt;/code&gt;. All that complexity, hidden behind a clean, fluent API.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Async Logging:&lt;/strong&gt; We immediately fire off the &lt;code&gt;chatLogService.saveLog()&lt;/code&gt; method. We don't wait for it to complete. This is a key performance optimization.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The Supporting Cast: Web Layer and Async Logging
&lt;/h3&gt;

&lt;p&gt;To make our application whole, we need a few more pieces.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Asynchronous Logging:&lt;/strong&gt; Why bother with &lt;code&gt;@Async&lt;/code&gt;? Imagine your database is slow or under heavy load. If we saved the log synchronously, the user would be stuck waiting for the database write to finish &lt;em&gt;after&lt;/em&gt; the AI has already responded. By making it asynchronous (&lt;code&gt;@EnableAsync&lt;/code&gt; on the main class is required), we send the response back to the user instantly while the database operation proceeds in a separate thread from our configured pool. It’s a small change that drastically improves user experience.&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;ChatLogService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ... repository ...&lt;/span&gt;

    &lt;span class="nd"&gt;@Async&lt;/span&gt; &lt;span class="c1"&gt;// Run this method in a background thread&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;saveLog&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;userPrompt&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;aiResponse&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;"Saving chat log in a separate thread..."&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;ChatLog&lt;/span&gt; &lt;span class="n"&gt;chatLog&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;ChatLog&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="c1"&gt;// ... set properties and save ...&lt;/span&gt;
        &lt;span class="n"&gt;chatLogRepository&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;chatLog&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The Controller:&lt;/strong&gt; Our &lt;code&gt;ChatController&lt;/code&gt; is a standard Spring MVC controller. It manages a simple in-memory list to display the conversation history on a Thymeleaf template. Its primary job is to orchestrate the flow between the user's browser and our backend services.&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;@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;ChatController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ... dependencies and chat history list ...&lt;/span&gt;

    &lt;span class="nd"&gt;@PostMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/chat"&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;handleChat&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@RequestParam&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;chatHistory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ChatMessage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"You"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt&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;aiResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;movieChatService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getMovieChatResponse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;chatHistory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ChatMessage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"MovieBot"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;aiResponse&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;"redirect:/chat"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Redirect-after-post pattern&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;
  
  
  Conclusion and What's Next
&lt;/h3&gt;

&lt;p&gt;And there you have it! In a surprisingly small amount of code, we've built the foundation of a robust, AI-powered chatbot. We've seen how to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Configure Spring AI to use Google's Gemini models via its OpenAI-compatible endpoint.&lt;/li&gt;
&lt;li&gt;  Craft effective system and user prompts to guide the AI's behavior.&lt;/li&gt;
&lt;li&gt;  Use the elegant &lt;code&gt;ChatClient&lt;/code&gt; to handle all the complex API communication.&lt;/li&gt;
&lt;li&gt;  Implement a smart, non-blocking logging strategy using &lt;code&gt;@Async&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is just the beginning. Our bot is smart, but it has no knowledge of any &lt;em&gt;specific&lt;/em&gt; data in our own database. In &lt;strong&gt;Part 2&lt;/strong&gt; of this series, we'll level up our application by introducing &lt;strong&gt;Retrieval-Augmented Generation (RAG)&lt;/strong&gt;. We'll teach our AI to query our &lt;code&gt;movies&lt;/code&gt; and &lt;code&gt;actors&lt;/code&gt; tables to answer questions with data it wasn't originally trained on. Stay tuned&lt;/p&gt;

</description>
      <category>ai</category>
      <category>java</category>
      <category>spring</category>
      <category>genai</category>
    </item>
    <item>
      <title>The Developer's Guide to Taming PostgreSQL: How We Tripled Throughput with PgBouncer</title>
      <dc:creator>HashCoder</dc:creator>
      <pubDate>Wed, 09 Jul 2025 10:41:42 +0000</pubDate>
      <link>https://dev.to/hgm88x/the-developers-guide-to-taming-postgresql-how-we-tripled-throughput-with-pgbouncer-dgm</link>
      <guid>https://dev.to/hgm88x/the-developers-guide-to-taming-postgresql-how-we-tripled-throughput-with-pgbouncer-dgm</guid>
      <description>&lt;h1&gt;
  
  
  The Developer's Guide to Taming PostgreSQL: How We Tripled Throughput with PgBouncer
&lt;/h1&gt;

&lt;p&gt;Last month, our card transaction processing service began failing during peak hours. The system, responsible for authorizing millions of dollars in transactions, was throwing &lt;code&gt;ConnectionTimeoutException&lt;/code&gt; errors. The culprit? Our PostgreSQL database was drowning under the sheer volume of connection requests from our fleet of Spring Boot microservices.&lt;/p&gt;

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

&lt;p&gt;After a deep dive, we implemented PgBouncer as a centralized connection pooler. The results were immediate and dramatic: we &lt;strong&gt;slashed our p95 latency by 75%&lt;/strong&gt; (from 1.2s to 300ms) and &lt;strong&gt;tripled our peak transaction authorization throughput&lt;/strong&gt;, completely eliminating connection timeouts.&lt;/p&gt;

&lt;p&gt;This isn't just another "how-to." This is the production-grade blueprint, complete with the configurations, JVM tuning, and architectural reasoning that actually matters when the system &lt;em&gt;cannot&lt;/em&gt; fail.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: Connection Pool Anarchy
&lt;/h2&gt;

&lt;p&gt;Our architecture consisted of 8 Spring Boot microservice instances, each configured with HikariCP's default &lt;code&gt;maximum-pool-size&lt;/code&gt; of 10. In theory, that's 80 concurrent connections. In reality, during traffic spikes and deployments, connection churn was causing massive contention on PostgreSQL.&lt;/p&gt;

&lt;p&gt;The symptoms were a classic sign of database saturation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  High &lt;code&gt;ConnectionTimeoutException&lt;/code&gt; error rates during peak traffic.&lt;/li&gt;
&lt;li&gt;  Excessive memory usage on the PostgreSQL server from managing hundreds of idle processes.&lt;/li&gt;
&lt;li&gt;  Slow query performance, even for indexed &lt;code&gt;SELECT&lt;/code&gt; statements.&lt;/li&gt;
&lt;li&gt;  Cascading failures as upstream services timed out waiting for our service.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Solution: PgBouncer as the Master Connection Manager
&lt;/h2&gt;

&lt;p&gt;PgBouncer is a lightweight, highly-optimized proxy that sits between your applications and PostgreSQL. It maintains a master pool of physical connections to the database and serves thousands of client connections by multiplexing them through this small, efficient pool. It's the traffic controller your database desperately needs.&lt;/p&gt;

&lt;p&gt;Here is our complete setup, ready to run in Docker.&lt;/p&gt;

&lt;h3&gt;
  
  
  Docker Compose Configuration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.8'&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;postgresql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:16-alpine&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-db&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;card_transactions_db&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app_user&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;secure_password&lt;/span&gt;
      &lt;span class="c1"&gt;# Enforce modern, secure authentication from the start&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_INITDB_ARGS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--auth-host=scram-sha-256"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;postgres_data:/var/lib/postgresql/data&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./postgresql.conf:/etc/postgresql/postgresql.conf&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres -c config_file=/etc/postgresql/postgresql.conf&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5432:5432"&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD-SHELL"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pg_isready&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-U&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;app_user&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-d&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;card_transactions_db"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&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;5s&lt;/span&gt;
      &lt;span class="na"&gt;retries&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;pgbouncer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pgbouncer/pgbouncer:1.21.0&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pgbouncer-proxy&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;6432:6432"&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;postgresql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# We mount our specific configs for production-like control&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./pgbouncer.ini:/etc/pgbouncer/pgbouncer.ini&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./userlist.txt:/etc/pgbouncer/userlist.txt&lt;/span&gt;

  &lt;span class="na"&gt;card-transaction-service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;card-transaction-service&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# Note: We connect to pgbouncer, not postgresql directly&lt;/span&gt;
      &lt;span class="na"&gt;SPRING_DATASOURCE_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jdbc:postgresql://pgbouncer:6432/card_transactions_db&lt;/span&gt;
      &lt;span class="na"&gt;SPRING_DATASOURCE_USERNAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app_user&lt;/span&gt;
      &lt;span class="na"&gt;SPRING_DATASOURCE_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;secure_password&lt;/span&gt;
      &lt;span class="na"&gt;SPRING_PROFILES_ACTIVE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8080:8080"&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pgbouncer&lt;/span&gt;
    &lt;span class="na"&gt;mem_limit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1g&lt;/span&gt;
    &lt;span class="na"&gt;mem_reservation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;512m&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;postgres_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Architect's Note on Startup:&lt;/strong&gt; Docker's &lt;code&gt;depends_on&lt;/code&gt; only ensures the &lt;em&gt;container&lt;/em&gt; has started, not that the PgBouncer process within is ready. For true production resilience, use a wrapper script with a tool like &lt;code&gt;wait-for-it.sh&lt;/code&gt; or build connection retry logic into your Spring application's startup sequence.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  PgBouncer Configuration (&lt;code&gt;pgbouncer.ini&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;This is where the magic happens. The defaults are not sufficient for production.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[databases]&lt;/span&gt;
&lt;span class="py"&gt;card_transactions_db&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;host=postgresql port=5432 dbname=card_transactions_db&lt;/span&gt;

&lt;span class="nn"&gt;[pgbouncer]&lt;/span&gt;
&lt;span class="c"&gt;# Network and Auth
&lt;/span&gt;&lt;span class="py"&gt;listen_addr&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;*&lt;/span&gt;
&lt;span class="py"&gt;listen_port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;6432&lt;/span&gt;
&lt;span class="py"&gt;auth_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;scram-sha-256&lt;/span&gt;
&lt;span class="py"&gt;auth_file&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/etc/pgbouncer/userlist.txt&lt;/span&gt;

&lt;span class="c"&gt;# Pooling Mode - CRITICAL
&lt;/span&gt;&lt;span class="py"&gt;pool_mode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;transaction&lt;/span&gt;

&lt;span class="c"&gt;# Connection Limits
&lt;/span&gt;&lt;span class="py"&gt;max_client_conn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;1000      # Max inbound connections from all apps&lt;/span&gt;
&lt;span class="py"&gt;default_pool_size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;20      # Connections per DB pool kept open to Postgres&lt;/span&gt;
&lt;span class="py"&gt;min_pool_size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;5           # Keep at least this many connections ready&lt;/span&gt;
&lt;span class="py"&gt;reserve_pool_size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;5       # Emergency connections for admin use&lt;/span&gt;
&lt;span class="py"&gt;max_db_connections&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;40     # A hard cap on physical connections to this PG instance&lt;/span&gt;

&lt;span class="c"&gt;# Timeouts (seconds)
&lt;/span&gt;&lt;span class="py"&gt;server_lifetime&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;3600            # Max life of a server connection&lt;/span&gt;
&lt;span class="py"&gt;server_idle_timeout&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;600         # Reconnect if idle for 10 mins&lt;/span&gt;
&lt;span class="py"&gt;server_connect_timeout&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;15&lt;/span&gt;
&lt;span class="py"&gt;server_login_retry&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;15&lt;/span&gt;
&lt;span class="py"&gt;client_login_timeout&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;60&lt;/span&gt;

&lt;span class="c"&gt;# Query and State Management
&lt;/span&gt;&lt;span class="py"&gt;server_reset_query&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;DISCARD ALL&lt;/span&gt;
&lt;span class="py"&gt;ignore_startup_parameters&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;extra_float_digits&lt;/span&gt;

&lt;span class="c"&gt;# Logging
&lt;/span&gt;&lt;span class="py"&gt;log_connections&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;1&lt;/span&gt;
&lt;span class="py"&gt;log_disconnections&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;1&lt;/span&gt;
&lt;span class="py"&gt;log_pooler_errors&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;1&lt;/span&gt;
&lt;span class="py"&gt;stats_period&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;60&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let me explain the most critical settings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;pool_mode = transaction&lt;/code&gt;&lt;/strong&gt;: This is the key to high throughput. A physical connection is assigned to a client for the duration of a single transaction and returned to the pool immediately upon &lt;code&gt;COMMIT&lt;/code&gt; or &lt;code&gt;ROLLBACK&lt;/code&gt;. This provides maximum reuse.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;server_reset_query = DISCARD ALL&lt;/code&gt;&lt;/strong&gt;: This is a non-negotiable security and stability setting. It ensures that any session-specific state (e.g., &lt;code&gt;SET&lt;/code&gt; variables, temporary tables) is wiped clean before a connection is reused by another client, preventing state leakage between transactions.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Connection Limits&lt;/strong&gt;: We've set &lt;code&gt;max_client_conn&lt;/code&gt; high to accept traffic from all service instances, but &lt;code&gt;default_pool_size&lt;/code&gt; low to protect PostgreSQL. The database now only ever sees a maximum of ~40 connections from our entire application fleet, regardless of how many microservices we scale up to.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The "Double Pooling" Dilemma: Why a Pool in Front of a Pool?
&lt;/h3&gt;

&lt;p&gt;You're right to ask: "If PgBouncer is my pooler, why do I still need HikariCP?" This isn't redundancy; it's a separation of concerns.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Application Pool (HikariCP):&lt;/strong&gt; Its job is to manage a pool of connections &lt;em&gt;to PgBouncer&lt;/em&gt;. These are cheap, fast, local network connections. Its purpose is to &lt;strong&gt;eliminate thread contention inside your Java application&lt;/strong&gt;. When a thread needs to run a transaction, it gets a connection &lt;em&gt;instantly&lt;/em&gt; from HikariCP without blocking. This pool should be sized for your application's peak internal concurrency.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Master Pool (PgBouncer):&lt;/strong&gt; Its job is to manage the truly scarce resource: &lt;strong&gt;physical connections to PostgreSQL&lt;/strong&gt;. It acts as the gatekeeper, taking the high volume of requests from all your service instances and efficiently funneling them through a small, controlled number of real database connections.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By separating these concerns, you get the best of both worlds: no thread starvation in your application and no connection exhaustion on your database.&lt;/p&gt;

&lt;h3&gt;
  
  
  Spring Boot Configuration (&lt;code&gt;application-docker.yml&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;The key is to configure HikariCP to be a good citizen in this new world. It no longer needs to be stingy with connections.&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;datasource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jdbc:postgresql://pgbouncer:6432/card_transactions_db&lt;/span&gt;
    &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app_user&lt;/span&gt;
    &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;secure_password&lt;/span&gt;
    &lt;span class="na"&gt;driver-class-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;org.postgresql.Driver&lt;/span&gt;
    &lt;span class="na"&gt;hikari&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;pool-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Hikari-Card-Service&lt;/span&gt;
      &lt;span class="c1"&gt;# Size this pool for your app's internal threads, not the DB.&lt;/span&gt;
      &lt;span class="na"&gt;minimum-idle&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
      &lt;span class="na"&gt;maximum-pool-size&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;
      &lt;span class="c1"&gt;# Aggressive timeouts because connections are to the local PgBouncer proxy.&lt;/span&gt;
      &lt;span class="na"&gt;idle-timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30000&lt;/span&gt;
      &lt;span class="na"&gt;max-lifetime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;600000&lt;/span&gt; &lt;span class="c1"&gt;# 10 minutes&lt;/span&gt;
      &lt;span class="na"&gt;connection-timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3000&lt;/span&gt;
      &lt;span class="na"&gt;validation-timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2000&lt;/span&gt;
      &lt;span class="na"&gt;connection-test-query&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SELECT &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;jpa&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;database-platform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;org.hibernate.dialect.PostgreSQLDialect&lt;/span&gt;
    &lt;span class="na"&gt;hibernate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;ddl-auto&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;validate&lt;/span&gt;
    &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;hibernate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;jdbc.batch_size&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;
        &lt;span class="na"&gt;order_inserts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;order_updates&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;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Architect's Note:&lt;/strong&gt; We've removed explicit &lt;code&gt;autocommit&lt;/code&gt; properties. In a modern Spring Boot application using &lt;code&gt;@Transactional&lt;/code&gt;, the Spring &lt;code&gt;DataSourceTransactionManager&lt;/code&gt; is the definitive authority. It correctly handles transaction boundaries, making these lower-level settings redundant and potentially confusing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  JVM Tuning for Containerized Performance (&lt;code&gt;Dockerfile&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Your application code runs on the JVM, and the JVM needs to be told it's running in a resource-constrained container.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; openjdk:21-jre-slim&lt;/span&gt;

&lt;span class="c"&gt;# Tell the JVM it's in a container and tune the GC for low-latency service work&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; JAVA_OPTS="-Xms512m -Xmx768m \&lt;/span&gt;
    -XX:+UseG1GC \
    -XX:MaxGCPauseMillis=100 \
    -XX:+UseStringDeduplication"

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; target/card-transaction-service-*.jar app.jar&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8080&lt;/span&gt;

&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["sh", "-c", "java $JAVA_OPTS -jar /app.jar"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Performance Testing Results
&lt;/h3&gt;

&lt;p&gt;The numbers speak for themselves.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Before PgBouncer&lt;/th&gt;
&lt;th&gt;After PgBouncer&lt;/th&gt;
&lt;th&gt;Improvement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Avg. Response Time&lt;/td&gt;
&lt;td&gt;450ms&lt;/td&gt;
&lt;td&gt;150ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;67% reduction&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;95th Percentile&lt;/td&gt;
&lt;td&gt;1.2s&lt;/td&gt;
&lt;td&gt;300ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;75% reduction&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Connection Errors&lt;/td&gt;
&lt;td&gt;~15-20%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Eliminated&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Active DB Connections&lt;/td&gt;
&lt;td&gt;80-120&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;20-25&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~75% reduction&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Load Testing with Artillery
&lt;/h3&gt;

&lt;p&gt;Verifying performance requires a realistic load test.&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;# load-test.yml&lt;/span&gt;
&lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;http://localhost:8080'&lt;/span&gt;
  &lt;span class="na"&gt;phases&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;300&lt;/span&gt;
      &lt;span class="na"&gt;arrivalRate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Ramp-up"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;600&lt;/span&gt;
      &lt;span class="na"&gt;arrivalRate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;250&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Sustained&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Peak&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Load"&lt;/span&gt;

&lt;span class="na"&gt;scenarios&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorize&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Card&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Transaction"&lt;/span&gt;
    &lt;span class="na"&gt;flow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;post&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/transactions/authorize"&lt;/span&gt;
          &lt;span class="na"&gt;json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;cardNumber&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;4{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;$randomDigits(15)&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
            &lt;span class="na"&gt;expiryMonth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;$randomInt(1,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;12)&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
            &lt;span class="na"&gt;expiryYear&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2026&lt;/span&gt;
            &lt;span class="na"&gt;cvv&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;$randomDigits(3)&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
            &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;$randomInt(10,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;500)&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}.00"&lt;/span&gt;
            &lt;span class="na"&gt;currency&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;USD"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Common Pitfalls and How to Avoid Them
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Pool Sizing Mismatch:&lt;/strong&gt; The most common mistake is sizing the application pool larger than PgBouncer's pool, creating a bottleneck.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Rule:&lt;/strong&gt; &lt;code&gt;Sum of all HikariCP pools&lt;/code&gt; should be comfortably less than &lt;code&gt;pgbouncer.max_client_conn&lt;/code&gt;. A single HikariCP pool (&lt;code&gt;maximum-pool-size&lt;/code&gt;) should be less than or equal to &lt;code&gt;pgbouncer.default_pool_size&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Using &lt;code&gt;SESSION&lt;/code&gt; Pool Mode:&lt;/strong&gt; Unless you are using features that &lt;em&gt;require&lt;/em&gt; a persistent connection for the entire session (like advisory locks), avoid &lt;code&gt;session&lt;/code&gt; mode. It defeats the purpose of high-throughput pooling. &lt;code&gt;transaction&lt;/code&gt; mode is almost always what you want.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Authentication Errors:&lt;/strong&gt; Ensure PostgreSQL's &lt;code&gt;pg_hba.conf&lt;/code&gt; and PgBouncer's &lt;code&gt;userlist.txt&lt;/code&gt; use the same authentication method (&lt;code&gt;scram-sha-256&lt;/code&gt; is the modern standard).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Prepared Statements (&lt;code&gt;PREPARE&lt;/code&gt;):&lt;/strong&gt; By default, PgBouncer in transaction mode doesn't support named prepared statements. Spring/Hibernate uses these under the hood. The PostgreSQL JDBC driver is smart enough to detect a proxy like PgBouncer and will fall back to unnamed statements, but you must use a recent version of the driver.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Centralize Connection Pooling:&lt;/strong&gt; Don't let individual applications manage scarce database connections. Use a dedicated tool like PgBouncer to act as a central gatekeeper.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Tune for a Layered World:&lt;/strong&gt; Configure your application pool (HikariCP) for in-app thread performance and your master pool (PgBouncer) for database resource protection.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;&lt;code&gt;transaction&lt;/code&gt; Mode is Your Default:&lt;/strong&gt; It provides the best balance of performance and compatibility for microservice architectures.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;State Management is Critical:&lt;/strong&gt; Use &lt;code&gt;DISCARD ALL&lt;/code&gt; to guarantee transactional isolation when connections are reused.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The performance gains we achieved weren't magic. They were the result of a systematic approach to identifying a bottleneck and applying the right architectural pattern—with the right configuration—to solve it.&lt;/p&gt;

&lt;p&gt;How have you tackled database connection scaling in your environment? Share your war stories in the comments.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
