<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>DevTech Team</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <id>https://thoughtfly.github.io/devtech/</id>
  <link href="https://thoughtfly.github.io/devtech/" rel="alternate"/>
  <link href="https://thoughtfly.github.io/devtech/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, DevTech Team</rights>
  <subtitle>Practical engineering guides for modern developers</subtitle>
  <title>DevTech Insights</title>
  <updated>2026-09-21T14:47:28.954Z</updated>
  <entry>
    <author>
      <name>DevTech Team</name>
    </author>
    <category term="Java" scheme="https://thoughtfly.github.io/devtech/categories/Java/"/>
    <category term="Backend Development" scheme="https://thoughtfly.github.io/devtech/categories/Java/Backend-Development/"/>
    <category term="Java" scheme="https://thoughtfly.github.io/devtech/tags/Java/"/>
    <category term="Spring Boot" scheme="https://thoughtfly.github.io/devtech/tags/Spring-Boot/"/>
    <category term="Reactive Programming" scheme="https://thoughtfly.github.io/devtech/tags/Reactive-Programming/"/>
    <category term="R2DBC" scheme="https://thoughtfly.github.io/devtech/tags/R2DBC/"/>
    <category term="Database" scheme="https://thoughtfly.github.io/devtech/tags/Database/"/>
    <category term="Non-blocking I/O" scheme="https://thoughtfly.github.io/devtech/tags/Non-blocking-I-O/"/>
    <content>
      <![CDATA[<h2 id="The-Evolution-of-Database-Access-in-Java"><a href="#The-Evolution-of-Database-Access-in-Java" class="headerlink" title="The Evolution of Database Access in Java"></a>The Evolution of Database Access in Java</h2><p>For over two decades, the Java ecosystem has relied heavily on JDBC as the standard for relational database access. It is synchronous, blocking, and proven. However, as applications scale to handle millions of concurrent connections, the traditional blocking I&#x2F;O model becomes a bottleneck. Enter reactive programming and, specifically for Java, <strong>R2DBC</strong> (Reactive Relational Database Connectivity).</p><p>In this post, we will dive deep into R2DBC, explore how it integrates with Spring Boot 6 and Spring Data R2DBC, and provide practical code examples to help you build high-throughput, non-blocking applications.</p><h2 id="What-is-R2DBC"><a href="#What-is-R2DBC" class="headerlink" title="What is R2DBC?"></a>What is R2DBC?</h2><p>R2DBC is a specification for reactive database drivers. It is analogous to JDBC but designed for the reactive stack. Just as JDBC provides a standard API for synchronous database access, R2DBC provides a standard API for <strong>asynchronous, non-blocking</strong> access.</p><h3 id="Key-Differences-from-JDBC"><a href="#Key-Differences-from-JDBC" class="headerlink" title="Key Differences from JDBC"></a>Key Differences from JDBC</h3><table><thead><tr><th>Feature</th><th>JDBC</th><th>R2DBC</th></tr></thead><tbody><tr><td><strong>I&#x2F;O Model</strong></td><td>Blocking</td><td>Non-blocking</td></tr><tr><td><strong>Threading</strong></td><td>One thread per connection</td><td>Event-loop friendly</td></tr><tr><td><strong>Concurrency</strong></td><td>Limited by thread pool</td><td>Scales to thousands of connections</td></tr><tr><td><strong>Backpressure</strong></td><td>No</td><td>Yes</td></tr><tr><td><strong>Return Types</strong></td><td><code>Connection</code>, <code>Statement</code></td><td><code>Flux</code>, <code>Mono</code></td></tr></tbody></table><h2 id="Why-Use-R2DBC-with-Spring-Boot"><a href="#Why-Use-R2DBC-with-Spring-Boot" class="headerlink" title="Why Use R2DBC with Spring Boot?"></a>Why Use R2DBC with Spring Boot?</h2><p>Spring Boot provides first-class support for reactive programming through the <strong>Spring WebFlux</strong> framework. When you combine Spring WebFlux with Spring Data R2DBC, you get an end-to-end reactive stack:</p><ol><li><strong>Non-blocking HTTP server</strong> (Netty)</li><li><strong>Non-blocking service layer</strong> (Reactive repositories)</li><li><strong>Non-blocking database access</strong> (R2DBC)</li></ol><p>This stack allows your application to handle a massive number of concurrent requests with minimal thread usage, reducing memory overhead and improving scalability.</p><h2 id="Setting-Up-a-Spring-Boot-Project-with-R2DBC"><a href="#Setting-Up-a-Spring-Boot-Project-with-R2DBC" class="headerlink" title="Setting Up a Spring Boot Project with R2DBC"></a>Setting Up a Spring Boot Project with R2DBC</h2><h3 id="Step-1-Add-Dependencies"><a href="#Step-1-Add-Dependencies" class="headerlink" title="Step 1: Add Dependencies"></a>Step 1: Add Dependencies</h3><p>To get started, you need to include the R2DBC driver for your database (e.g., PostgreSQL, MySQL, H2) and the Spring Data R2DBC starter.</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- pom.xml --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependencies</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- Spring Boot WebFlux --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-webflux<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">&lt;!-- Spring Data R2DBC --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-data-r2dbc<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">&lt;!-- R2DBC Driver for PostgreSQL --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.postgresql<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>r2dbc-postgresql<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">scope</span>&gt;</span>runtime<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">&lt;!-- H2 for development/testing --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>io.r2dbc<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>r2dbc-h2<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">scope</span>&gt;</span>runtime<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependencies</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="Step-2-Configure-the-Database-Connection"><a href="#Step-2-Configure-the-Database-Connection" class="headerlink" title="Step 2: Configure the Database Connection"></a>Step 2: Configure the Database Connection</h3><p>In <code>application.yml</code>, you need to specify the R2DBC URL and credentials. Note that R2DBC uses a different URL format than JDBC.</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># application.yml</span></span><br><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">r2dbc:</span></span><br><span class="line">    <span class="attr">url:</span> <span class="string">r2dbc:postgresql://localhost:5432/mydb</span></span><br><span class="line">    <span class="attr">username:</span> <span class="string">postgres</span></span><br><span class="line">    <span class="attr">password:</span> <span class="string">secret</span></span><br><span class="line">    <span class="attr">pool:</span></span><br><span class="line">      <span class="attr">enabled:</span> <span class="literal">true</span></span><br><span class="line">      <span class="attr">max-size:</span> <span class="number">20</span></span><br><span class="line">      <span class="attr">max-idle-time:</span> <span class="string">30s</span></span><br></pre></td></tr></table></figure><h2 id="Building-a-Reactive-Repository"><a href="#Building-a-Reactive-Repository" class="headerlink" title="Building a Reactive Repository"></a>Building a Reactive Repository</h2><p>Spring Data R2DBC provides reactive repositories that return <code>Flux</code> (for multiple results) and <code>Mono</code> (for single results).</p><h3 id="Define-the-Entity"><a href="#Define-the-Entity" class="headerlink" title="Define the Entity"></a>Define the Entity</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> org.springframework.data.annotation.Id;</span><br><span class="line"><span class="keyword">import</span> org.springframework.data.relational.core.mapping.Table;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Table(&quot;users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">User</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Id</span></span><br><span class="line">    <span class="keyword">private</span> Long id;</span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line">    <span class="keyword">private</span> String email;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Constructor, getters, and setters</span></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">User</span><span class="params">(Long id, String name, String email)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.id = id;</span><br><span class="line">        <span class="built_in">this</span>.name = name;</span><br><span class="line">        <span class="built_in">this</span>.email = email;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Getters and setters omitted for brevity</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Define-the-Repository"><a href="#Define-the-Repository" class="headerlink" title="Define the Repository"></a>Define the Repository</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> org.springframework.data.r2dbc.repository.R2dbcRepository;</span><br><span class="line"><span class="keyword">import</span> reactor.core.publisher.Flux;</span><br><span class="line"><span class="keyword">import</span> reactor.core.publisher.Mono;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">UserRepository</span> <span class="keyword">extends</span> <span class="title class_">R2dbcRepository</span>&lt;User, Long&gt; &#123;</span><br><span class="line"></span><br><span class="line">    Flux&lt;User&gt; <span class="title function_">findByEmail</span><span class="params">(String email)</span>;</span><br><span class="line"></span><br><span class="line">    Mono&lt;User&gt; <span class="title function_">findByName</span><span class="params">(String name)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Notice that we don’t need to implement this interface. Spring Data R2DBC generates the implementation at runtime.</p><h2 id="Creating-a-Reactive-Service-Layer"><a href="#Creating-a-Reactive-Service-Layer" class="headerlink" title="Creating a Reactive Service Layer"></a>Creating a Reactive Service Layer</h2><p>Your service layer should also be reactive, using <code>Mono</code> and <code>Flux</code> throughout.</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> org.springframework.stereotype.Service;</span><br><span class="line"><span class="keyword">import</span> org.springframework.transaction.annotation.Transactional;</span><br><span class="line"><span class="keyword">import</span> reactor.core.publisher.Flux;</span><br><span class="line"><span class="keyword">import</span> reactor.core.publisher.Mono;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserRepository userRepository;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserService</span><span class="params">(UserRepository userRepository)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userRepository = userRepository;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> Flux&lt;User&gt; <span class="title function_">findAll</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userRepository.findAll();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> Mono&lt;User&gt; <span class="title function_">findById</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userRepository.findById(id);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> Mono&lt;User&gt; <span class="title function_">createUser</span><span class="params">(User user)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userRepository.save(user);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> Mono&lt;Void&gt; <span class="title function_">deleteUser</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userRepository.deleteById(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Building-a-Reactive-REST-Controller"><a href="#Building-a-Reactive-REST-Controller" class="headerlink" title="Building a Reactive REST Controller"></a>Building a Reactive REST Controller</h2><p>With Spring WebFlux, we use <code>@RestController</code> and return reactive types directly. The framework handles the asynchronous response.</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> org.springframework.web.bind.annotation.*;</span><br><span class="line"><span class="keyword">import</span> reactor.core.publisher.Flux;</span><br><span class="line"><span class="keyword">import</span> reactor.core.publisher.Mono;</span><br><span class="line"></span><br><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/api/users&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserController</span><span class="params">(UserService userService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userService = userService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping</span></span><br><span class="line">    <span class="keyword">public</span> Flux&lt;User&gt; <span class="title function_">getAllUsers</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.findAll();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Mono&lt;User&gt; <span class="title function_">getUserById</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.findById(id);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@PostMapping</span></span><br><span class="line">    <span class="keyword">public</span> Mono&lt;User&gt; <span class="title function_">createUser</span><span class="params">(<span class="meta">@RequestBody</span> User user)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.createUser(user);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@DeleteMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Mono&lt;Void&gt; <span class="title function_">deleteUser</span><span class="params">(<span class="meta">@PathVariable</span> Long id)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.deleteUser(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Advanced-Using-DatabaseClient-for-Complex-Queries"><a href="#Advanced-Using-DatabaseClient-for-Complex-Queries" class="headerlink" title="Advanced: Using DatabaseClient for Complex Queries"></a>Advanced: Using DatabaseClient for Complex Queries</h2><p>While Spring Data R2DBC repositories are great for simple CRUD operations, you may need more control over SQL queries. In such cases, use <code>DatabaseClient</code>.</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> org.springframework.data.r2dbc.core.DatabaseClient;</span><br><span class="line"><span class="keyword">import</span> reactor.core.publisher.Flux;</span><br><span class="line"><span class="keyword">import</span> reactor.core.publisher.Mono;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AdvancedUserService</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> DatabaseClient databaseClient;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">AdvancedUserService</span><span class="params">(DatabaseClient databaseClient)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.databaseClient = databaseClient;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> Flux&lt;User&gt; <span class="title function_">findUsersByName</span><span class="params">(String name)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> databaseClient.sql(<span class="string">&quot;SELECT * FROM users WHERE name LIKE :name&quot;</span>)</span><br><span class="line">                .bind(<span class="string">&quot;name&quot;</span>, <span class="string">&quot;%&quot;</span> + name + <span class="string">&quot;%&quot;</span>)</span><br><span class="line">                .map((row, metadata) -&gt; <span class="keyword">new</span> <span class="title class_">User</span>(</span><br><span class="line">                        row.get(<span class="string">&quot;id&quot;</span>, Long.class),</span><br><span class="line">                        row.get(<span class="string">&quot;name&quot;</span>, String.class),</span><br><span class="line">                        row.get(<span class="string">&quot;email&quot;</span>, String.class)</span><br><span class="line">                ))</span><br><span class="line">                .all();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> Mono&lt;Integer&gt; <span class="title function_">insertUser</span><span class="params">(User user)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> databaseClient.sql(<span class="string">&quot;INSERT INTO users (name, email) VALUES (:name, :email)&quot;</span>)</span><br><span class="line">                .bind(<span class="string">&quot;name&quot;</span>, user.getName())</span><br><span class="line">                .bind(<span class="string">&quot;email&quot;</span>, user.getEmail())</span><br><span class="line">                .fetch()</span><br><span class="line">                .rowsUpdated();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Transaction-Management-in-Reactive-Applications"><a href="#Transaction-Management-in-Reactive-Applications" class="headerlink" title="Transaction Management in Reactive Applications"></a>Transaction Management in Reactive Applications</h2><p>Transaction management in reactive applications requires special attention. You cannot use <code>@Transactional</code> in the same way as in synchronous Spring MVC. Instead, use <code>TransactionManager</code> explicitly.</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> org.springframework.r2dbc.connection.R2dbcTransactionManager;</span><br><span class="line"><span class="keyword">import</span> org.springframework.transaction.reactive.TransactionTemplate;</span><br><span class="line"><span class="keyword">import</span> reactor.core.publisher.Mono;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AccountService</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> DatabaseClient databaseClient;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> TransactionTemplate transactionTemplate;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">AccountService</span><span class="params">(DatabaseClient databaseClient, R2dbcTransactionManager transactionManager)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.databaseClient = databaseClient;</span><br><span class="line">        <span class="built_in">this</span>.transactionTemplate = <span class="keyword">new</span> <span class="title class_">TransactionTemplate</span>(transactionManager);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> Mono&lt;Void&gt; <span class="title function_">transferFunds</span><span class="params">(Long fromId, Long toId, <span class="type">double</span> amount)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> transactionTemplate.execute(status -&gt; </span><br><span class="line">            databaseClient.sql(<span class="string">&quot;UPDATE accounts SET balance = balance - :amount WHERE id = :id&quot;</span>)</span><br><span class="line">                    .bind(<span class="string">&quot;amount&quot;</span>, amount)</span><br><span class="line">                    .bind(<span class="string">&quot;id&quot;</span>, fromId)</span><br><span class="line">                    .fetch()</span><br><span class="line">                    .rowsUpdated()</span><br><span class="line">                    .then(</span><br><span class="line">                        databaseClient.sql(<span class="string">&quot;UPDATE accounts SET balance = balance + :amount WHERE id = :id&quot;</span>)</span><br><span class="line">                                .bind(<span class="string">&quot;amount&quot;</span>, amount)</span><br><span class="line">                                .bind(<span class="string">&quot;id&quot;</span>, toId)</span><br><span class="line">                                .fetch()</span><br><span class="line">                                .rowsUpdated()</span><br><span class="line">                    )</span><br><span class="line">                    .then()</span><br><span class="line">        );</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Connection-Pooling"><a href="#Connection-Pooling" class="headerlink" title="Connection Pooling"></a>Connection Pooling</h2><p>R2DBC supports connection pooling out of the box. Spring Boot auto-configures a connection pool when you add the R2DBC starter. You can customize the pool settings in <code>application.yml</code> as shown earlier.</p><p>For production, consider using <strong>Netty</strong> or <strong>Lettuce</strong> as your reactive client. Spring Boot defaults to Netty for WebFlux, but you can switch to other implementations if needed.</p><h2 id="Common-Pitfalls-and-Best-Practices"><a href="#Common-Pitfalls-and-Best-Practices" class="headerlink" title="Common Pitfalls and Best Practices"></a>Common Pitfalls and Best Practices</h2><h3 id="1-Avoid-Blocking-Calls"><a href="#1-Avoid-Blocking-Calls" class="headerlink" title="1. Avoid Blocking Calls"></a>1. Avoid Blocking Calls</h3><p>Never mix blocking and non-blocking code. If you must use a blocking library, wrap it in <code>Mono.fromSupplier()</code> or use <code>subscribeOn(Schedulers.boundedElastic())</code>.</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Bad: Blocking call in reactive chain</span></span><br><span class="line"><span class="keyword">public</span> Mono&lt;User&gt; <span class="title function_">findUser</span><span class="params">(String id)</span> &#123;</span><br><span class="line">    <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> blockingRepository.findById(id); <span class="comment">// This blocks the event loop!</span></span><br><span class="line">    <span class="keyword">return</span> Mono.just(user);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Good: Properly isolated blocking call</span></span><br><span class="line"><span class="keyword">public</span> Mono&lt;User&gt; <span class="title function_">findUser</span><span class="params">(String id)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> Mono.fromSupplier(() -&gt; blockingRepository.findById(id))</span><br><span class="line">               .subscribeOn(Schedulers.boundedElastic());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-Use-Backpressure-Wisely"><a href="#2-Use-Backpressure-Wisely" class="headerlink" title="2. Use Backpressure Wisely"></a>2. Use Backpressure Wisely</h3><p>Reactive streams support backpressure. Ensure your downstream consumers can handle the data rate. Use operators like <code>onBackpressureBuffer()</code> or <code>limitRate()</code> if needed.</p><h3 id="3-Handle-Errors-Gracefully"><a href="#3-Handle-Errors-Gracefully" class="headerlink" title="3. Handle Errors Gracefully"></a>3. Handle Errors Gracefully</h3><p>Use <code>onErrorResume()</code>, <code>onErrorReturn()</code>, and <code>doOnError()</code> to handle errors in your reactive chains.</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> Mono&lt;User&gt; <span class="title function_">getUser</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> userRepository.findById(id)</span><br><span class="line">            .onErrorResume(WebFluxResponseStatusException.class, e -&gt; Mono.empty())</span><br><span class="line">            .switchIfEmpty(Mono.error(<span class="keyword">new</span> <span class="title class_">UserNotFoundException</span>(id)));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-Test-with-H2"><a href="#4-Test-with-H2" class="headerlink" title="4. Test with H2"></a>4. Test with H2</h3><p>Use H2 in reactive mode for testing. It’s lightweight and supports R2DBC.</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># application-test.yml</span></span><br><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">r2dbc:</span></span><br><span class="line">    <span class="attr">url:</span> <span class="string">r2dbc:h2:mem:///testdb;DB_CLOSE_DELAY=-1</span></span><br><span class="line">    <span class="attr">username:</span> <span class="string">sa</span></span><br><span class="line">    <span class="attr">password:</span> </span><br></pre></td></tr></table></figure><h2 id="Performance-Considerations"><a href="#Performance-Considerations" class="headerlink" title="Performance Considerations"></a>Performance Considerations</h2><p>R2DBC shines in scenarios with high concurrency and low latency requirements. However, for simple CRUD applications with low traffic, the added complexity may not be worth it. JDBC with a connection pool (e.g., HikariCP) is often sufficient and easier to debug.</p><p>Benchmark your application under load to determine if R2DBC provides the necessary performance gains.</p><h2 id="Key-Takeaways"><a href="#Key-Takeaways" class="headerlink" title="Key Takeaways"></a>Key Takeaways</h2><ul><li><strong>R2DBC</strong> is the reactive counterpart to JDBC, enabling non-blocking database access.</li><li><strong>Spring Data R2DBC</strong> provides reactive repositories that return <code>Flux</code> and <code>Mono</code>.</li><li><strong>Spring WebFlux</strong> complements R2DBC by providing a non-blocking web stack.</li><li>Use <strong>DatabaseClient</strong> for complex queries that go beyond simple CRUD.</li><li>Avoid mixing blocking and non-blocking code; isolate blocking calls using <code>Schedulers.boundedElastic()</code>.</li><li><strong>Connection pooling</strong> is auto-configured but should be tuned for production.</li><li>R2DBC is ideal for high-concurrency, low-latency applications but may add unnecessary complexity for simpler use cases.</li></ul><p>By mastering R2DBC and reactive programming in Spring Boot, you can build scalable, resilient applications that efficiently handle thousands of concurrent connections.</p>]]>
    </content>
    <id>https://thoughtfly.github.io/devtech/2026/09/20/r2dbc-and-reactive-relational-access-in-spring-boot/</id>
    <link href="https://thoughtfly.github.io/devtech/2026/09/20/r2dbc-and-reactive-relational-access-in-spring-boot/"/>
    <published>2026-09-20T16:00:00.000Z</published>
    <summary>Learn how to implement reactive database access using R2DBC in Spring Boot. Compare with JDBC, explore configuration, and write non-blocking SQL queries for...</summary>
    <title>Mastering R2DBC and Reactive Relational Access in Spring Boot</title>
    <updated>2026-09-21T14:47:28.954Z</updated>
  </entry>
  <entry>
    <author>
      <name>DevTech Team</name>
    </author>
    <category term="Java" scheme="https://thoughtfly.github.io/devtech/categories/Java/"/>
    <category term="Java" scheme="https://thoughtfly.github.io/devtech/tags/Java/"/>
    <category term="Java 21" scheme="https://thoughtfly.github.io/devtech/tags/Java-21/"/>
    <category term="Virtual Threads" scheme="https://thoughtfly.github.io/devtech/tags/Virtual-Threads/"/>
    <category term="Concurrency" scheme="https://thoughtfly.github.io/devtech/tags/Concurrency/"/>
    <category term="Performance" scheme="https://thoughtfly.github.io/devtech/tags/Performance/"/>
    <content>
      <![CDATA[<h2 id="Introduction"><a href="#Introduction" class="headerlink" title="Introduction"></a>Introduction</h2><p>For over two decades, Java developers have relied on the platform thread model to handle concurrent workloads. While effective, this model has inherent limitations: each thread maps to an OS thread, consuming significant memory and resources. As applications grow more demanding, the traditional approach struggles to scale efficiently.</p><p>Enter virtual threads, introduced in Java 21 as a standard feature. Virtual threads are lightweight, managed by the JVM rather than the OS, and can handle millions of concurrent tasks with minimal overhead. But migrating existing code isn’t always straightforward.</p><p>In this guide, we’ll walk through the process of migrating blocking code to virtual threads, covering everything from assessment to deployment.</p><h2 id="Understanding-the-Problem"><a href="#Understanding-the-Problem" class="headerlink" title="Understanding the Problem"></a>Understanding the Problem</h2><p>Before diving into migration, let’s understand why virtual threads matter. Platform threads are expensive:</p><ul><li><strong>Memory overhead</strong>: Each platform thread typically requires 1MB of stack space</li><li><strong>Context switching</strong>: OS-level thread switching is costly</li><li><strong>Limited scalability</strong>: Creating thousands of platform threads leads to resource exhaustion</li></ul><p>Virtual threads solve these problems by:</p><ul><li><strong>Lightweight memory</strong>: Only a few hundred bytes per virtual thread</li><li><strong>JVM-managed scheduling</strong>: No OS context switching overhead</li><li><strong>Massive concurrency</strong>: Handle millions of concurrent operations</li></ul><h2 id="Step-1-Assess-Your-Codebase"><a href="#Step-1-Assess-Your-Codebase" class="headerlink" title="Step 1: Assess Your Codebase"></a>Step 1: Assess Your Codebase</h2><p>The first step is identifying blocking operations in your application. Common culprits include:</p><ul><li>Database queries</li><li>HTTP client calls</li><li>File I&#x2F;O operations</li><li>Network socket operations</li><li>Synchronous API calls</li></ul><h3 id="Identifying-Blocking-Code"><a href="#Identifying-Blocking-Code" class="headerlink" title="Identifying Blocking Code"></a>Identifying Blocking Code</h3><p>Use profiling tools to identify hotspots. Here’s a simple example of blocking code that’s a good candidate for virtual threads:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LegacyService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">fetchData</span><span class="params">(String url)</span> &#123;</span><br><span class="line">        <span class="comment">// Blocking HTTP call</span></span><br><span class="line">        HttpResponse&lt;String&gt; response = HttpRequest</span><br><span class="line">            .newBuilder()</span><br><span class="line">            .uri(URI.create(url))</span><br><span class="line">            .GET()</span><br><span class="line">            .timeout(Duration.ofSeconds(<span class="number">30</span>))</span><br><span class="line">            .build()</span><br><span class="line">            .send();</span><br><span class="line">        <span class="keyword">return</span> response.body();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> List&lt;User&gt; <span class="title function_">getUsers</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// Blocking database query</span></span><br><span class="line">        <span class="keyword">try</span> (<span class="type">Connection</span> <span class="variable">conn</span> <span class="operator">=</span> dataSource.getConnection()) &#123;</span><br><span class="line">            <span class="type">Statement</span> <span class="variable">stmt</span> <span class="operator">=</span> conn.createStatement();</span><br><span class="line">            <span class="type">ResultSet</span> <span class="variable">rs</span> <span class="operator">=</span> stmt.executeQuery(<span class="string">&quot;SELECT * FROM users&quot;</span>);</span><br><span class="line">            List&lt;User&gt; users = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">            <span class="keyword">while</span> (rs.next()) &#123;</span><br><span class="line">                users.add(<span class="keyword">new</span> <span class="title class_">User</span>(rs.getLong(<span class="string">&quot;id&quot;</span>), rs.getString(<span class="string">&quot;name&quot;</span>)));</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> users;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (SQLException e) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(e);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Step-2-Update-Dependencies"><a href="#Step-2-Update-Dependencies" class="headerlink" title="Step 2: Update Dependencies"></a>Step 2: Update Dependencies</h2><p>Ensure your project uses Java 21 or later and update your build configuration:</p><h3 id="Maven-Configuration"><a href="#Maven-Configuration" class="headerlink" title="Maven Configuration"></a>Maven Configuration</h3><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">properties</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">java.version</span>&gt;</span>21<span class="tag">&lt;/<span class="name">java.version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">maven.compiler.source</span>&gt;</span>21<span class="tag">&lt;/<span class="name">maven.compiler.source</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">maven.compiler.target</span>&gt;</span>21<span class="tag">&lt;/<span class="name">maven.compiler.target</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">properties</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="Gradle-Configuration"><a href="#Gradle-Configuration" class="headerlink" title="Gradle Configuration"></a>Gradle Configuration</h3><figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">java &#123;</span><br><span class="line">    toolchain &#123;</span><br><span class="line">        languageVersion = JavaLanguageVersion.of(<span class="number">21</span>)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Step-3-Convert-Thread-Creation"><a href="#Step-3-Convert-Thread-Creation" class="headerlink" title="Step 3: Convert Thread Creation"></a>Step 3: Convert Thread Creation</h2><p>The most straightforward migration involves replacing platform thread creation with virtual threads.</p><h3 id="Before-Platform-Threads"><a href="#Before-Platform-Threads" class="headerlink" title="Before: Platform Threads"></a>Before: Platform Threads</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">ExecutorService</span> <span class="variable">executor</span> <span class="operator">=</span> Executors.newFixedThreadPool(<span class="number">100</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// Submit tasks using platform threads</span></span><br><span class="line">executor.submit(() -&gt; &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">result</span> <span class="operator">=</span> service.fetchData(<span class="string">&quot;https://api.example.com/data&quot;</span>);</span><br><span class="line">    processResult(result);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h3 id="After-Virtual-Threads"><a href="#After-Virtual-Threads" class="headerlink" title="After: Virtual Threads"></a>After: Virtual Threads</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Option 1: Using Thread.ofVirtual()</span></span><br><span class="line"><span class="type">ExecutorService</span> <span class="variable">executor</span> <span class="operator">=</span> Executors.newVirtualThreadPerTaskExecutor();</span><br><span class="line"></span><br><span class="line">executor.submit(() -&gt; &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">result</span> <span class="operator">=</span> service.fetchData(<span class="string">&quot;https://api.example.com/data&quot;</span>);</span><br><span class="line">    processResult(result);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// Option 2: Using Thread.startVirtualThread() for one-off tasks</span></span><br><span class="line">Thread.startVirtualThread(() -&gt; &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">result</span> <span class="operator">=</span> service.fetchData(<span class="string">&quot;https://api.example.com/data&quot;</span>);</span><br><span class="line">    processResult(result);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h2 id="Step-4-Handle-ThreadLocal-Variables"><a href="#Step-4-Handle-ThreadLocal-Variables" class="headerlink" title="Step 4: Handle ThreadLocal Variables"></a>Step 4: Handle ThreadLocal Variables</h2><p>Virtual threads don’t share ThreadLocal values with platform threads. If your code relies on ThreadLocal, you need to adapt:</p><h3 id="Problem-ThreadLocal-in-Virtual-Threads"><a href="#Problem-ThreadLocal-in-Virtual-Threads" class="headerlink" title="Problem: ThreadLocal in Virtual Threads"></a>Problem: ThreadLocal in Virtual Threads</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RequestContext</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> ThreadLocal&lt;User&gt; currentUser = <span class="keyword">new</span> <span class="title class_">ThreadLocal</span>&lt;&gt;();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">setUser</span><span class="params">(User user)</span> &#123;</span><br><span class="line">        currentUser.set(user);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> User <span class="title function_">getUser</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> currentUser.get();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Solution-Use-InheritableThreadLocal-or-Scoped-Values"><a href="#Solution-Use-InheritableThreadLocal-or-Scoped-Values" class="headerlink" title="Solution: Use InheritableThreadLocal or Scoped Values"></a>Solution: Use InheritableThreadLocal or Scoped Values</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Option 1: InheritableThreadLocal (works with virtual threads)</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> InheritableThreadLocal&lt;User&gt; currentUser = <span class="keyword">new</span> <span class="title class_">InheritableThreadLocal</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line"><span class="comment">// Option 2: Scoped Values (Java 21+, preferred for performance)</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> ScopedValue&lt;User&gt; CURRENT_USER = ScopedValue.newInstance();</span><br><span class="line"></span><br><span class="line">ScopedValue.where(CURRENT_USER, user)</span><br><span class="line">    .run(() -&gt; &#123;</span><br><span class="line">        <span class="comment">// Code that accesses CURRENT_USER.get()</span></span><br><span class="line">        processRequest();</span><br><span class="line">    &#125;);</span><br></pre></td></tr></table></figure><h2 id="Step-5-Address-Synchronized-Blocks"><a href="#Step-5-Address-Synchronized-Blocks" class="headerlink" title="Step 5: Address Synchronized Blocks"></a>Step 5: Address Synchronized Blocks</h2><p>Virtual threads can cause issues with synchronized blocks due to pinning. When a virtual thread holds a monitor, it can’t be unpinned, blocking the carrier thread.</p><h3 id="Problem-Synchronized-with-Virtual-Threads"><a href="#Problem-Synchronized-with-Virtual-Threads" class="headerlink" title="Problem: Synchronized with Virtual Threads"></a>Problem: Synchronized with Virtual Threads</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SharedResource</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> List&lt;String&gt; data = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">(String item)</span> &#123;</span><br><span class="line">        <span class="comment">// This can pin the virtual thread</span></span><br><span class="line">        data.add(item);</span><br><span class="line">        simulateBlockingOperation();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Solution-Use-ReentrantLock-or-Reduce-Synchronization"><a href="#Solution-Use-ReentrantLock-or-Reduce-Synchronization" class="headerlink" title="Solution: Use ReentrantLock or Reduce Synchronization"></a>Solution: Use ReentrantLock or Reduce Synchronization</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.concurrent.locks.ReentrantLock;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SharedResource</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> List&lt;String&gt; data = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">ReentrantLock</span> <span class="variable">lock</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ReentrantLock</span>();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">(String item)</span> &#123;</span><br><span class="line">        lock.lock();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            data.add(item);</span><br><span class="line">            simulateBlockingOperation();</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            lock.unlock();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Step-6-Migrate-Connection-Pools"><a href="#Step-6-Migrate-Connection-Pools" class="headerlink" title="Step 6: Migrate Connection Pools"></a>Step 6: Migrate Connection Pools</h2><p>Traditional connection pools are designed for platform threads. With virtual threads, you need pools that support virtual thread awareness.</p><h3 id="JDBC-Connection-Pool-Configuration"><a href="#JDBC-Connection-Pool-Configuration" class="headerlink" title="JDBC Connection Pool Configuration"></a>JDBC Connection Pool Configuration</h3><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># application.yml</span></span><br><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">datasource:</span></span><br><span class="line">    <span class="attr">hikari:</span></span><br><span class="line">      <span class="comment"># Virtual threads don&#x27;t need large pools</span></span><br><span class="line">      <span class="attr">maximum-pool-size:</span> <span class="number">20</span></span><br><span class="line">      <span class="attr">minimum-idle:</span> <span class="number">5</span></span><br><span class="line">      <span class="attr">connection-timeout:</span> <span class="number">30000</span></span><br></pre></td></tr></table></figure><h3 id="Custom-Virtual-Thread-Aware-Pool"><a href="#Custom-Virtual-Thread-Aware-Pool" class="headerlink" title="Custom Virtual Thread-Aware Pool"></a>Custom Virtual Thread-Aware Pool</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VirtualThreadAwarePool</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Queue&lt;Connection&gt; available = <span class="keyword">new</span> <span class="title class_">ArrayDeque</span>&lt;&gt;();</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">int</span> maxSize;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">VirtualThreadAwarePool</span><span class="params">(<span class="type">int</span> maxSize)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.maxSize = maxSize;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> Connection <span class="title function_">getConnection</span><span class="params">()</span> <span class="keyword">throws</span> SQLException &#123;</span><br><span class="line">        <span class="comment">// Try to get from pool</span></span><br><span class="line">        <span class="type">Connection</span> <span class="variable">conn</span> <span class="operator">=</span> available.poll();</span><br><span class="line">        <span class="keyword">if</span> (conn != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> conn;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// Create new connection if under limit</span></span><br><span class="line">        <span class="keyword">if</span> (available.size() &lt; maxSize) &#123;</span><br><span class="line">            <span class="keyword">return</span> createConnection();</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// Wait for connection with virtual thread awareness</span></span><br><span class="line">        <span class="keyword">return</span> waitForConnection();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Step-7-Update-HTTP-Clients"><a href="#Step-7-Update-HTTP-Clients" class="headerlink" title="Step 7: Update HTTP Clients"></a>Step 7: Update HTTP Clients</h2><p>Modern HTTP clients work well with virtual threads. Here’s how to configure them:</p><h3 id="Using-HttpClient-Java-11"><a href="#Using-HttpClient-Java-11" class="headerlink" title="Using HttpClient (Java 11+)"></a>Using HttpClient (Java 11+)</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// HttpClient works seamlessly with virtual threads</span></span><br><span class="line"><span class="type">HttpClient</span> <span class="variable">client</span> <span class="operator">=</span> HttpClient.newHttpClient();</span><br><span class="line"></span><br><span class="line"><span class="comment">// Use with virtual threads</span></span><br><span class="line"><span class="type">ExecutorService</span> <span class="variable">executor</span> <span class="operator">=</span> Executors.newVirtualThreadPerTaskExecutor();</span><br><span class="line"></span><br><span class="line">List&lt;CompletableFuture&lt;String&gt;&gt; futures = urls.stream()</span><br><span class="line">    .map(url -&gt; CompletableFuture.supplyAsync(() -&gt; &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="type">HttpRequest</span> <span class="variable">request</span> <span class="operator">=</span> HttpRequest.newBuilder()</span><br><span class="line">                .uri(URI.create(url))</span><br><span class="line">                .GET()</span><br><span class="line">                .build();</span><br><span class="line">            HttpResponse&lt;String&gt; response = client.send(request, </span><br><span class="line">                BodyHandlers.ofString());</span><br><span class="line">            <span class="keyword">return</span> response.body();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(e);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;, executor))</span><br><span class="line">    .toList();</span><br><span class="line"></span><br><span class="line"><span class="comment">// Wait for all results</span></span><br><span class="line">List&lt;String&gt; results = futures.stream()</span><br><span class="line">    .map(CompletableFuture::join)</span><br><span class="line">    .toList();</span><br></pre></td></tr></table></figure><h3 id="Using-OkHttp-with-Virtual-Threads"><a href="#Using-OkHttp-with-Virtual-Threads" class="headerlink" title="Using OkHttp with Virtual Threads"></a>Using OkHttp with Virtual Threads</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">OkHttpClient</span> <span class="variable">client</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">OkHttpClient</span>.Builder()</span><br><span class="line">    .connectTimeout(<span class="number">30</span>, TimeUnit.SECONDS)</span><br><span class="line">    .readTimeout(<span class="number">30</span>, TimeUnit.SECONDS)</span><br><span class="line">    .build();</span><br><span class="line"></span><br><span class="line"><span class="type">ExecutorService</span> <span class="variable">executor</span> <span class="operator">=</span> Executors.newVirtualThreadPerTaskExecutor();</span><br><span class="line"></span><br><span class="line">List&lt;Call&gt; calls = urls.stream()</span><br><span class="line">    .map(url -&gt; &#123;</span><br><span class="line">        <span class="type">Request</span> <span class="variable">request</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Request</span>.Builder()</span><br><span class="line">            .url(url)</span><br><span class="line">            .build();</span><br><span class="line">        <span class="keyword">return</span> client.newCall(request);</span><br><span class="line">    &#125;)</span><br><span class="line">    .toList();</span><br><span class="line"></span><br><span class="line">List&lt;Response&gt; responses = calls.stream()</span><br><span class="line">    .map(call -&gt; &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> executor.submit(call::execute).get();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(e);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;)</span><br><span class="line">    .toList();</span><br></pre></td></tr></table></figure><h2 id="Step-8-Monitor-and-Tune"><a href="#Step-8-Monitor-and-Tune" class="headerlink" title="Step 8: Monitor and Tune"></a>Step 8: Monitor and Tune</h2><p>After migration, monitor your application to ensure virtual threads are performing as expected.</p><h3 id="Key-Metrics-to-Track"><a href="#Key-Metrics-to-Track" class="headerlink" title="Key Metrics to Track"></a>Key Metrics to Track</h3><ul><li><strong>Thread count</strong>: Should be much higher than platform threads</li><li><strong>CPU usage</strong>: Should be similar or lower due to better scheduling</li><li><strong>Memory usage</strong>: Should decrease significantly</li><li><strong>Response times</strong>: Should improve under load</li></ul><h3 id="JMX-Monitoring"><a href="#JMX-Monitoring" class="headerlink" title="JMX Monitoring"></a>JMX Monitoring</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Monitor virtual threads via JMX</span></span><br><span class="line"><span class="type">ThreadMXBean</span> <span class="variable">threadMXBean</span> <span class="operator">=</span> ManagementFactory.getThreadMXBean();</span><br><span class="line"></span><br><span class="line"><span class="type">long</span> <span class="variable">virtualThreadCount</span> <span class="operator">=</span> Arrays.stream(threadMXBean.getAllThreadIds())</span><br><span class="line">    .mapToObj(id -&gt; threadMXBean.getThreadInfo(id, Long.MAX_VALUE))</span><br><span class="line">    .filter(info -&gt; info != <span class="literal">null</span> &amp;&amp; info.getThreadType() == Thread.Type.VIRTUAL)</span><br><span class="line">    .count();</span><br><span class="line"></span><br><span class="line">System.out.println(<span class="string">&quot;Virtual threads: &quot;</span> + virtualThreadCount);</span><br></pre></td></tr></table></figure><h2 id="Common-Pitfalls-and-Solutions"><a href="#Common-Pitfalls-and-Solutions" class="headerlink" title="Common Pitfalls and Solutions"></a>Common Pitfalls and Solutions</h2><h3 id="Pitfall-1-Thread-Dump-Analysis"><a href="#Pitfall-1-Thread-Dump-Analysis" class="headerlink" title="Pitfall 1: Thread Dump Analysis"></a>Pitfall 1: Thread Dump Analysis</h3><p>Thread dumps show virtual threads differently. Use the <code>-XX:+PrintVirtualThreads</code> flag for better visibility.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Generate thread dump with virtual thread details</span></span><br><span class="line">jcmd &lt;pid&gt; Thread.<span class="built_in">print</span> -v</span><br></pre></td></tr></table></figure><h3 id="Pitfall-2-Blocking-in-Synchronized-Blocks"><a href="#Pitfall-2-Blocking-in-Synchronized-Blocks" class="headerlink" title="Pitfall 2: Blocking in Synchronized Blocks"></a>Pitfall 2: Blocking in Synchronized Blocks</h3><p>As mentioned earlier, avoid synchronized blocks with virtual threads. Use ReentrantLock or other concurrent utilities.</p><h3 id="Pitfall-3-Excessive-Thread-Creation"><a href="#Pitfall-3-Excessive-Thread-Creation" class="headerlink" title="Pitfall 3: Excessive Thread Creation"></a>Pitfall 3: Excessive Thread Creation</h3><p>While virtual threads are lightweight, creating millions of them still has costs. Use appropriate pool sizes for shared resources.</p><h3 id="Pitfall-4-Testing-with-Limited-Concurrency"><a href="#Pitfall-4-Testing-with-Limited-Concurrency" class="headerlink" title="Pitfall 4: Testing with Limited Concurrency"></a>Pitfall 4: Testing with Limited Concurrency</h3><p>Ensure your tests exercise high concurrency to validate virtual thread behavior.</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">testHighConcurrency</span><span class="params">()</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">    <span class="type">ExecutorService</span> <span class="variable">executor</span> <span class="operator">=</span> Executors.newVirtualThreadPerTaskExecutor();</span><br><span class="line">    </span><br><span class="line">    <span class="type">int</span> <span class="variable">threadCount</span> <span class="operator">=</span> <span class="number">10000</span>;</span><br><span class="line">    <span class="type">CountDownLatch</span> <span class="variable">startLatch</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CountDownLatch</span>(<span class="number">1</span>);</span><br><span class="line">    <span class="type">CountDownLatch</span> <span class="variable">doneLatch</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CountDownLatch</span>(threadCount);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; threadCount; i++) &#123;</span><br><span class="line">        executor.submit(() -&gt; &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                startLatch.await();</span><br><span class="line">                service.processRequest();</span><br><span class="line">            &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(e);</span><br><span class="line">            &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                doneLatch.countDown();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    startLatch.countDown();</span><br><span class="line">    doneLatch.await(<span class="number">60</span>, TimeUnit.SECONDS);</span><br><span class="line">    </span><br><span class="line">    executor.shutdown();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Performance-Comparison"><a href="#Performance-Comparison" class="headerlink" title="Performance Comparison"></a>Performance Comparison</h2><p>Here’s a comparison of platform threads vs. virtual threads:</p><table><thead><tr><th>Metric</th><th>Platform Threads</th><th>Virtual Threads</th></tr></thead><tbody><tr><td>Memory per thread</td><td>~1MB</td><td>~500 bytes</td></tr><tr><td>Max concurrent threads</td><td>~1,000</td><td>~1,000,000+</td></tr><tr><td>Context switch cost</td><td>High</td><td>Low</td></tr><tr><td>Setup time</td><td>Slow</td><td>Fast</td></tr><tr><td>Blocking handling</td><td>Poor</td><td>Excellent</td></tr></tbody></table><h2 id="Migration-Checklist"><a href="#Migration-Checklist" class="headerlink" title="Migration Checklist"></a>Migration Checklist</h2><ol><li><input disabled="" type="checkbox"> Identify all blocking operations</li><li><input disabled="" type="checkbox"> Update to Java 21+</li><li><input disabled="" type="checkbox"> Replace thread pool configurations</li><li><input disabled="" type="checkbox"> Handle ThreadLocal variables</li><li><input disabled="" type="checkbox"> Replace synchronized blocks</li><li><input disabled="" type="checkbox"> Update connection pools</li><li><input disabled="" type="checkbox"> Configure HTTP clients</li><li><input disabled="" type="checkbox"> Add monitoring</li><li><input disabled="" type="checkbox"> Run load tests</li><li><input disabled="" type="checkbox"> Monitor production metrics</li></ol><h2 id="Key-Takeaways"><a href="#Key-Takeaways" class="headerlink" title="Key Takeaways"></a>Key Takeaways</h2><ul><li><strong>Virtual threads are lightweight</strong>: They enable massive concurrency with minimal memory overhead</li><li><strong>Migration is often straightforward</strong>: Many blocking operations work seamlessly with virtual threads</li><li><strong>Watch out for synchronization</strong>: Avoid synchronized blocks; use ReentrantLock instead</li><li><strong>ThreadLocal needs attention</strong>: Use InheritableThreadLocal or Scoped Values</li><li><strong>Connection pools shrink</strong>: Virtual threads don’t need large pools for blocking operations</li><li><strong>Monitor carefully</strong>: Track thread counts, CPU, memory, and response times</li><li><strong>Test with high concurrency</strong>: Validate behavior under realistic load conditions</li></ul><p>Virtual threads represent a paradigm shift in Java concurrency. By following this step-by-step guide, you can migrate your blocking code effectively and unlock the scalability benefits of modern Java concurrency.</p>]]>
    </content>
    <id>https://thoughtfly.github.io/devtech/2026/09/19/migrating-blocking-code-to-virtual-threads-a-step-by-step-guide/</id>
    <link href="https://thoughtfly.github.io/devtech/2026/09/19/migrating-blocking-code-to-virtual-threads-a-step-by-step-guide/"/>
    <published>2026-09-19T16:00:00.000Z</published>
    <summary>Learn how to migrate legacy Java applications from platform threads to virtual threads. Practical guide with code examples, pitfalls, and performance tips.</summary>
    <title>Migrating Blocking Code to Virtual Threads: A Step-by-Step Guide</title>
    <updated>2026-09-21T14:46:52.852Z</updated>
  </entry>
  <entry>
    <author>
      <name>DevTech Team</name>
    </author>
    <category term="Java" scheme="https://thoughtfly.github.io/devtech/categories/Java/"/>
    <category term="Java" scheme="https://thoughtfly.github.io/devtech/tags/Java/"/>
    <category term="Spring Boot" scheme="https://thoughtfly.github.io/devtech/tags/Spring-Boot/"/>
    <category term="Performance" scheme="https://thoughtfly.github.io/devtech/tags/Performance/"/>
    <category term="CRaC" scheme="https://thoughtfly.github.io/devtech/tags/CRaC/"/>
    <category term="Startup Optimization" scheme="https://thoughtfly.github.io/devtech/tags/Startup-Optimization/"/>
    <category term="JVM" scheme="https://thoughtfly.github.io/devtech/tags/JVM/"/>
    <content>
      <![CDATA[<h2 id="The-Startup-Bottleneck"><a href="#The-Startup-Bottleneck" class="headerlink" title="The Startup Bottleneck"></a>The Startup Bottleneck</h2><p>If you’ve spent any time working with Spring Boot in production, you know the pain. You deploy a new version, and for the next 10 to 30 seconds, your service is invisible. It’s not just a delay; it’s a window of vulnerability. During this time, load balancers think your instance is down. Autoscaling policies might trigger incorrectly. Users experience timeouts. In high-throughput microservices architectures, where thousands of instances spin up and down daily, this startup latency adds up to significant wasted resources and degraded user experience.</p><p>Traditionally, engineers have tackled this problem with warmers, speculative scaling, or keeping idle instances alive. But these are workarounds, not solutions. They consume resources without delivering value.</p><p>Enter <strong>Coordinated Restore at Checkpoint (CRaC)</strong>. This is not just another optimization library; it is a fundamental shift in how the Java Virtual Machine (JVM) manages state. By leveraging OS-level process checkpointing, CRaC allows you to save the entire state of a running JVM and restore it instantly, bypassing the expensive initialization phase entirely.</p><p>In this post, we will explore how CRaC works, how to integrate it with Spring Boot, and the practical trade-offs you need to consider before adopting it in your production environment.</p><h2 id="What-is-CRaC"><a href="#What-is-CRaC" class="headerlink" title="What is CRaC?"></a>What is CRaC?</h2><p>CRaC is a project under the OpenJDK umbrella that provides APIs for checkpointing and restoring Java processes. The core idea is simple but powerful: instead of starting a Java application from scratch (loading classes, initializing the JVM, running static initializers, establishing database connections), you save a snapshot of the application after it has fully initialized, and then restore that snapshot when a new instance is needed.</p><p>The process involves two distinct phases:</p><ol><li><strong>Checkpointing:</strong> The running JVM is paused, its memory state is saved to disk, and the process exits. This is similar to hibernating a laptop, but at the OS level.</li><li><strong>Restoration:</strong> A new JVM process is spawned from the checkpoint. The OS restores the memory state, and the application resumes execution as if it had never stopped. Startup time drops from seconds to milliseconds.</li></ol><h3 id="How-It-Differs-from-Traditional-Serialization"><a href="#How-It-Differs-from-Traditional-Serialization" class="headerlink" title="How It Differs from Traditional Serialization"></a>How It Differs from Traditional Serialization</h3><p>It is crucial to distinguish CRaC from standard Java serialization or frameworks like Hessian or Kryo. Traditional serialization requires you to manually define how objects are saved and restored. You deal with schema evolution, class versioning, and complex object graphs. CRaC operates at the JVM level. It snapshots the entire heap, including native memory, thread stacks, and open file descriptors. You do not write a single line of serialization code. The JVM handles the complexity.</p><h2 id="Why-Spring-Boot"><a href="#Why-Spring-Boot" class="headerlink" title="Why Spring Boot?"></a>Why Spring Boot?</h2><p>Spring Boot is the de facto standard for Java enterprise development, but it is also one of the heaviest frameworks to start. A typical Spring Boot application performs a significant amount of work during startup:</p><ul><li><strong>Classpath Scanning:</strong> Scanning jars for components, annotations, and configurations.</li><li><strong>Bean Initialization:</strong> Creating and wiring thousands of beans.</li><li><strong>Embedded Server Startup:</strong> Initializing Tomcat, Jetty, or Undertow.</li><li><strong>Database Connection Pooling:</strong> Establishing connections to databases, Redis, Kafka, etc.</li><li><strong>Security Filters:</strong> Setting up Spring Security chains.</li></ul><p>Each of these steps introduces latency. For a large microservice, the cumulative effect can be substantial. CRaC is particularly effective for Spring Boot because it allows you to checkpoint the application <em>after</em> all this heavy lifting is complete. When you restore, you skip all of it.</p><h2 id="Setting-Up-CRaC-with-Spring-Boot"><a href="#Setting-Up-CRaC-with-Spring-Boot" class="headerlink" title="Setting Up CRaC with Spring Boot"></a>Setting Up CRaC with Spring Boot</h2><p>Integrating CRaC into a Spring Boot project is straightforward, but it requires specific configurations at both the JVM and application levels. Let’s walk through the steps.</p><h3 id="1-JVM-Requirements"><a href="#1-JVM-Requirements" class="headerlink" title="1. JVM Requirements"></a>1. JVM Requirements</h3><p>CRaC requires a JVM that supports it. As of Java 21, CRaC is available as a commercial feature in Oracle JDK and as an experimental feature in OpenJDK. For production use, you typically need a JDK build with CRaC support, such as the one provided by Adoptium or Oracle. Ensure your Dockerfile or deployment script uses the correct JDK version.</p><h3 id="2-Adding-Dependencies"><a href="#2-Adding-Dependencies" class="headerlink" title="2. Adding Dependencies"></a>2. Adding Dependencies</h3><p>You need to add the CRaC API and a coordinator to your project. For Spring Boot, the <code>crac-spring-boot</code> starter simplifies the integration. Add the following to your <code>pom.xml</code>:</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.crac<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>crac<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>0.1.4<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-actuator<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><p>Note: Check the latest version on Maven Central, as CRaC is still evolving.</p><h3 id="3-Configuring-the-Application"><a href="#3-Configuring-the-Application" class="headerlink" title="3. Configuring the Application"></a>3. Configuring the Application</h3><p>Spring Boot provides auto-configuration for CRaC. You need to enable it in your <code>application.properties</code> or <code>application.yml</code>:</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">crac:</span></span><br><span class="line">    <span class="attr">enabled:</span> <span class="literal">true</span></span><br><span class="line">    <span class="attr">restore-path:</span> <span class="string">/tmp/crac-restore</span></span><br><span class="line">    <span class="attr">checkpoint-path:</span> <span class="string">/tmp/crac-checkpoint</span></span><br></pre></td></tr></table></figure><ul><li><strong><code>enabled</code></strong>: Turns on CRaC support.</li><li><strong><code>restore-path</code></strong>: The directory where the checkpoint will be restored from.</li><li><strong><code>checkpoint-path</code></strong>: The directory where the checkpoint will be saved.</li></ul><h3 id="4-Implementing-a-Coordinator"><a href="#4-Implementing-a-Coordinator" class="headerlink" title="4. Implementing a Coordinator"></a>4. Implementing a Coordinator</h3><p>CRaC requires a <strong>Coordinator</strong> to manage resources that cannot be automatically checkpointed. For example, database connections, thread pools, and external service clients need to be explicitly managed. Spring Boot’s auto-configuration handles many common resources, but you may need to create custom coordinators for specific beans.</p><p>Here’s an example of a custom coordinator for a hypothetical external service:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ExternalServiceCoordinator</span> <span class="keyword">extends</span> <span class="title class_">Coordinator</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> ExternalService service;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">ExternalServiceCoordinator</span><span class="params">(ExternalService service)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.service = service;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">beforeCheckpoint</span><span class="params">(Context&lt;? extends Coordinator&gt; context)</span> &#123;</span><br><span class="line">        <span class="comment">// Close connections, flush buffers</span></span><br><span class="line">        service.closeConnection();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">afterRestore</span><span class="params">(Context&lt;? extends Coordinator&gt; context)</span> &#123;</span><br><span class="line">        <span class="comment">// Reopen connections, restore state</span></span><br><span class="line">        service.openConnection();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>The <code>beforeCheckpoint</code> method is called before the JVM is saved, allowing you to clean up resources. The <code>afterRestore</code> method is called after the JVM is restored, allowing you to reinitialize resources.</p><h2 id="The-Checkpoint-and-Restore-Lifecycle"><a href="#The-Checkpoint-and-Restore-Lifecycle" class="headerlink" title="The Checkpoint and Restore Lifecycle"></a>The Checkpoint and Restore Lifecycle</h2><p>Understanding the lifecycle is key to using CRaC effectively. Here’s what happens when you trigger a checkpoint:</p><ol><li><strong>Trigger:</strong> An actuator endpoint or an external orchestrator (like Kubernetes) signals the application to checkpoint.</li><li><strong>Pause:</strong> The JVM pauses all threads.</li><li><strong>Checkpoint:</strong> The JVM saves the memory state to disk. Coordinators are invoked to save and restore state.</li><li><strong>Exit:</strong> The process exits.</li><li><strong>Restore:</strong> A new JVM process is started with the same command-line arguments. The OS restores the memory state from the checkpoint.</li><li><strong>Resume:</strong> The application resumes execution from the point where it was paused.</li></ol><h3 id="Important-No-Code-Execution-During-Checkpoint"><a href="#Important-No-Code-Execution-During-Checkpoint" class="headerlink" title="Important: No Code Execution During Checkpoint"></a>Important: No Code Execution During Checkpoint</h3><p>It is critical to understand that <strong>no application code runs during the checkpoint process</strong>. The application is paused. Therefore, you cannot perform long-running operations in <code>beforeCheckpoint</code>. Keep it fast. Close connections, flush buffers, but do not make network calls that might hang.</p><h2 id="Performance-Gains-Real-World-Numbers"><a href="#Performance-Gains-Real-World-Numbers" class="headerlink" title="Performance Gains: Real-World Numbers"></a>Performance Gains: Real-World Numbers</h2><p>Let’s look at some concrete numbers. In a benchmark of a typical Spring Boot microservice with 500 beans, a PostgreSQL connection, and a Redis client:</p><ul><li><strong>Standard Startup:</strong> 12.5 seconds</li><li><strong>CRaC Restore:</strong> 0.8 seconds</li></ul><p>That’s a <strong>93% reduction</strong> in startup time. For a service with 100 instances, this means you can scale out 100 instances in less than a minute instead of 20 minutes. This is a game-changer for autoscaling scenarios, such as handling sudden traffic spikes or recovering from failures.</p><h2 id="Challenges-and-Trade-offs"><a href="#Challenges-and-Trade-offs" class="headerlink" title="Challenges and Trade-offs"></a>Challenges and Trade-offs</h2><p>While CRaC is powerful, it is not a silver bullet. There are several challenges to consider.</p><h3 id="1-Checkpoint-Size"><a href="#1-Checkpoint-Size" class="headerlink" title="1. Checkpoint Size"></a>1. Checkpoint Size</h3><p>The checkpoint is a snapshot of the entire JVM heap. For a large application, this can be several gigabytes. Storing and transferring these checkpoints requires significant disk I&#x2F;O and network bandwidth. If your storage is slow, the checkpoint and restore times will suffer.</p><p><strong>Mitigation:</strong> Use fast SSDs or NVMe storage. Consider compressing the checkpoint if your workload allows it.</p><h3 id="2-Resource-Management"><a href="#2-Resource-Management" class="headerlink" title="2. Resource Management"></a>2. Resource Management</h3><p>As mentioned, you need to manage resources that cannot be automatically checkpointed. This adds complexity to your application. If you forget to close a connection in <code>beforeCheckpoint</code>, you might end up with duplicate connections after restoration, leading to resource leaks or database errors.</p><p><strong>Mitigation:</strong> Thoroughly test your coordinators. Use Spring Boot’s auto-configuration where possible, and audit custom coordinators regularly.</p><h3 id="3-Compatibility"><a href="#3-Compatibility" class="headerlink" title="3. Compatibility"></a>3. Compatibility</h3><p>CRaC is not compatible with all Java libraries. Some libraries use native code or JNI, which may not be checkpointable. Libraries that rely on thread-local state or custom class loaders can also cause issues.</p><p><strong>Mitigation:</strong> Test your application with CRaC early in the development cycle. Check the CRaC compatibility list and community forums for known issues with specific libraries.</p><h3 id="4-Debugging"><a href="#4-Debugging" class="headerlink" title="4. Debugging"></a>4. Debugging</h3><p>Debugging a checkpointed application is harder than debugging a standard application. If something goes wrong during restoration, the stack trace may not be helpful because the application is resuming from a saved state.</p><p><strong>Mitigation:</strong> Use logging extensively. Log the state of your application before checkpointing and after restoration. Develop a robust health check mechanism.</p><h2 id="Kubernetes-Integration"><a href="#Kubernetes-Integration" class="headerlink" title="Kubernetes Integration"></a>Kubernetes Integration</h2><p>One of the most compelling use cases for CRaC is Kubernetes. Kubernetes already supports process checkpointing and restoration via <strong>CRIU</strong> (Checkpoint and Restore in Userspace). CRaC leverages CRIU under the hood.</p><p>To use CRaC with Kubernetes, you need to:</p><ol><li>Ensure your nodes have CRIU installed.</li><li>Configure your pod spec to use the <code>checkpoint</code> lifecycle hook.</li><li>Store checkpoints in a shared volume (e.g., NFS, PVC) so that any node can restore from any checkpoint.</li></ol><p>Here’s a snippet of a Kubernetes pod spec with CRaC support:</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Pod</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">crac-app</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">containers:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">app</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">my-app:latest</span></span><br><span class="line">    <span class="attr">volumeMounts:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">checkpoint-volume</span></span><br><span class="line">      <span class="attr">mountPath:</span> <span class="string">/tmp/crac-checkpoint</span></span><br><span class="line">  <span class="attr">volumes:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">checkpoint-volume</span></span><br><span class="line">    <span class="attr">persistentVolumeClaim:</span></span><br><span class="line">      <span class="attr">claimName:</span> <span class="string">checkpoint-pvc</span></span><br></pre></td></tr></table></figure><p>This setup allows Kubernetes to checkpoint a pod and restore it on any node in the cluster, enabling near-instantaneous scaling and failover.</p><h2 id="Best-Practices-for-Production"><a href="#Best-Practices-for-Production" class="headerlink" title="Best Practices for Production"></a>Best Practices for Production</h2><p>If you decide to adopt CRaC, here are some best practices to ensure a smooth transition:</p><ul><li><strong>Start Small:</strong> Begin with a non-critical service. Learn from the experience before applying it to high-traffic services.</li><li><strong>Monitor Checkpoint Times:</strong> Track the time it takes to checkpoint and restore. If these times increase, investigate resource leaks or checkpoint size growth.</li><li><strong>Use Health Checks:</strong> Implement liveness and readiness probes that account for CRaC. After restoration, the application may need a few milliseconds to become fully ready.</li><li><strong>Test Failure Scenarios:</strong> Simulate node failures and network partitions. Ensure your application can recover gracefully from a restored state.</li><li><strong>Keep JDK Updated:</strong> CRaC is an active area of development. Keep your JDK up to date to benefit from bug fixes and performance improvements.</li></ul><h2 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h2><p>CRaC represents a paradigm shift in Java application performance. By eliminating the startup penalty, it enables more responsive, scalable, and cost-effective microservices architectures. While it introduces new complexities around resource management and compatibility, the benefits are substantial for the right workload.</p><p>For Spring Boot applications that suffer from long startup times, CRaC is worth investigating. It is not a fit for every application, but for those that need to scale quickly and recover fast, it can be a transformative technology.</p><h2 id="Key-Takeaways"><a href="#Key-Takeaways" class="headerlink" title="Key Takeaways"></a>Key Takeaways</h2><ul><li><strong>CRaC reduces startup time by up to 90%</strong> by checkpointing a fully initialized JVM and restoring it on demand.</li><li><strong>Integration with Spring Boot</strong> is simplified through auto-configuration, but custom coordinators are needed for non-standard resources.</li><li><strong>Checkpoint size and I&#x2F;O</strong> are critical factors; use fast storage to minimize checkpoint and restore times.</li><li><strong>Resource management</strong> is essential; ensure all external connections are properly closed before checkpointing and reopened after restoration.</li><li><strong>Kubernetes integration</strong> is seamless with CRIU, enabling instant scaling and failover across nodes.</li><li><strong>Start with a non-critical service</strong> to learn the nuances before adopting CRaC in production-critical applications.</li></ul>]]>
    </content>
    <id>https://thoughtfly.github.io/devtech/2026/09/18/crac-cutting-spring-boot-startup-time-with-coordinated-restore-at-checkpoint/</id>
    <link href="https://thoughtfly.github.io/devtech/2026/09/18/crac-cutting-spring-boot-startup-time-with-coordinated-restore-at-checkpoint/"/>
    <published>2026-09-18T16:00:00.000Z</published>
    <summary>Learn how to use Coordinated Restore at Checkpoint (CRaC) to reduce Spring Boot startup time by up to 90%. A practical guide for Java developers.</summary>
    <title>CRaC: Cutting Spring Boot Startup Time with Coordinated Restore at Checkpoint</title>
    <updated>2026-09-21T14:46:52.852Z</updated>
  </entry>
  <entry>
    <author>
      <name>DevTech Team</name>
    </author>
    <category term="Java" scheme="https://thoughtfly.github.io/devtech/categories/Java/"/>
    <category term="Java" scheme="https://thoughtfly.github.io/devtech/tags/Java/"/>
    <category term="Spring Boot" scheme="https://thoughtfly.github.io/devtech/tags/Spring-Boot/"/>
    <category term="Cloud Native" scheme="https://thoughtfly.github.io/devtech/tags/Cloud-Native/"/>
    <category term="GraalVM" scheme="https://thoughtfly.github.io/devtech/tags/GraalVM/"/>
    <category term="Native Image" scheme="https://thoughtfly.github.io/devtech/tags/Native-Image/"/>
    <category term="Performance" scheme="https://thoughtfly.github.io/devtech/tags/Performance/"/>
    <content>
      <![CDATA[<h2 id="Introduction"><a href="#Introduction" class="headerlink" title="Introduction"></a>Introduction</h2><p>In the world of cloud-native applications, startup time and memory footprint are critical metrics that directly impact deployment costs, scaling efficiency, and user experience. Traditional JVM-based applications, while powerful, often struggle with cold starts in containerized environments and consume significant memory resources.</p><p>Enter GraalVM Native Image—a revolutionary technology that transforms Java bytecode into standalone native executables before runtime. When combined with Spring Boot 3.x, this approach unlocks dramatic improvements in startup performance and reduced memory usage, making it ideal for serverless functions, microservices, and Kubernetes deployments.</p><p>In this comprehensive guide, we will explore everything you need to know about building native images with Spring Boot and GraalVM, from initial project setup to production deployment.</p><h2 id="Why-Use-Native-Images-with-Spring-Boot"><a href="#Why-Use-Native-Images-with-Spring-Boot" class="headerlink" title="Why Use Native Images with Spring Boot?"></a>Why Use Native Images with Spring Boot?</h2><h3 id="The-Performance-Advantage"><a href="#The-Performance-Advantage" class="headerlink" title="The Performance Advantage"></a>The Performance Advantage</h3><p>Traditional Spring Boot applications require a full JVM to run, which means:</p><ul><li>Longer startup times (often 2-5 seconds for complex applications)</li><li>Higher memory consumption (typically 256MB-1GB minimum)</li><li>Slower cold starts in serverless environments</li></ul><p>Native images eliminate the JVM overhead by ahead-of-time (AOT) compilation:</p><ul><li>Startup times under 100 milliseconds</li><li>Memory usage reduced by 50-80%</li><li>Instant cold starts in containerized environments</li><li>Lower infrastructure costs at scale</li></ul><h3 id="When-Should-You-Consider-Native-Images"><a href="#When-Should-You-Consider-Native-Images" class="headerlink" title="When Should You Consider Native Images?"></a>When Should You Consider Native Images?</h3><p>Native images are particularly beneficial for:</p><ul><li>Serverless functions and event-driven architectures</li><li>Microservices with high scaling demands</li><li>Applications requiring rapid response times</li><li>Cost-sensitive cloud deployments</li><li>Kubernetes workloads with many replicas</li></ul><p>However, they may not be suitable for:</p><ul><li>Applications relying heavily on dynamic class loading</li><li>Legacy codebases with complex reflection usage</li><li>Projects requiring frequent hot-reloading during development</li></ul><h2 id="Prerequisites"><a href="#Prerequisites" class="headerlink" title="Prerequisites"></a>Prerequisites</h2><p>Before diving into the implementation, ensure you have the following:</p><ol><li><strong>Java 17 or higher</strong> (preferably Java 21 for production)</li><li><strong>Maven 3.8+</strong> or <strong>Gradle 7+</strong></li><li><strong>Docker</strong> for containerized builds (recommended)</li><li><strong>GraalVM</strong> with Native Image tool installed</li><li><strong>Spring Boot 3.2+</strong> project</li></ol><h2 id="Setting-Up-Your-Project"><a href="#Setting-Up-Your-Project" class="headerlink" title="Setting Up Your Project"></a>Setting Up Your Project</h2><h3 id="Option-1-Using-Spring-Initializr"><a href="#Option-1-Using-Spring-Initializr" class="headerlink" title="Option 1: Using Spring Initializr"></a>Option 1: Using Spring Initializr</h3><p>The easiest way to start is through Spring Initializr. Visit <a href="https://start.spring.io/">start.spring.io</a> and configure the following:</p><ul><li><strong>Project</strong>: Maven or Gradle</li><li><strong>Language</strong>: Java</li><li><strong>Spring Boot</strong>: 3.2.x or later</li><li><strong>Group</strong>: com.example</li><li><strong>Artifact</strong>: native-demo</li><li><strong>Dependencies</strong>:<ul><li>Spring Web</li><li>Spring Data JPA (if using database)</li><li>Spring Boot Actuator</li><li>GraalVM Native Image support</li></ul></li></ul><h3 id="Option-2-Manual-Configuration"><a href="#Option-2-Manual-Configuration" class="headerlink" title="Option 2: Manual Configuration"></a>Option 2: Manual Configuration</h3><p>If you prefer to configure manually, add the following to your <code>pom.xml</code>:</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">parent</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-parent<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>3.2.4<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">parent</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">dependencies</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-web<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-actuator<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependencies</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">build</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">plugins</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">plugin</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.graalvm.buildtools<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>native-maven-plugin<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">version</span>&gt;</span>0.9.28<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">extensions</span>&gt;</span>true<span class="tag">&lt;/<span class="name">extensions</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">configuration</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">mainClass</span>&gt;</span>com.example.nativedemo.NativeDemoApplication<span class="tag">&lt;/<span class="name">mainClass</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">configuration</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">plugin</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">plugins</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">build</span>&gt;</span></span><br></pre></td></tr></table></figure><p>For Gradle projects, add to your <code>build.gradle</code>:</p><figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">plugins &#123;</span><br><span class="line">    id <span class="string">&#x27;org.springframework.boot&#x27;</span> version <span class="string">&#x27;3.2.4&#x27;</span></span><br><span class="line">    id <span class="string">&#x27;io.spring.dependency-management&#x27;</span> version <span class="string">&#x27;1.1.4&#x27;</span></span><br><span class="line">    id <span class="string">&#x27;org.graalvm.buildtools.native&#x27;</span> version <span class="string">&#x27;0.9.28&#x27;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">dependencies &#123;</span><br><span class="line">    implementation <span class="string">&#x27;org.springframework.boot:spring-boot-starter-web&#x27;</span></span><br><span class="line">    implementation <span class="string">&#x27;org.springframework.boot:spring-boot-starter-actuator&#x27;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Building-Your-First-Native-Image"><a href="#Building-Your-First-Native-Image" class="headerlink" title="Building Your First Native Image"></a>Building Your First Native Image</h2><h3 id="Local-Build-with-Maven"><a href="#Local-Build-with-Maven" class="headerlink" title="Local Build with Maven"></a>Local Build with Maven</h3><p>To build a native image locally, ensure GraalVM is properly configured:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Set GraalVM home</span></span><br><span class="line"><span class="built_in">export</span> JAVA_HOME=/path/to/graalvm-java17</span><br><span class="line"><span class="built_in">export</span> PATH=<span class="variable">$JAVA_HOME</span>/bin:<span class="variable">$PATH</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Verify GraalVM installation</span></span><br><span class="line">java -version</span><br><span class="line"></span><br><span class="line"><span class="comment"># Build native image</span></span><br><span class="line">./mvnw package -Pnative</span><br></pre></td></tr></table></figure><h3 id="Local-Build-with-Docker"><a href="#Local-Build-with-Docker" class="headerlink" title="Local Build with Docker"></a>Local Build with Docker</h3><p>For consistent builds across environments, use Docker:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./mvnw package -Pnative -Dnative.image.docker.build=<span class="literal">true</span></span><br></pre></td></tr></table></figure><p>This creates a Docker container that builds the native image, ensuring reproducibility.</p><h3 id="Understanding-the-Build-Output"><a href="#Understanding-the-Build-Output" class="headerlink" title="Understanding the Build Output"></a>Understanding the Build Output</h3><p>After a successful build, you will find the native executable in:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">target/native-demo</span><br></pre></td></tr></table></figure><p>On Linux, the file will have no extension. On macOS&#x2F;Windows, it will be <code>native-demo.exe</code>.</p><h2 id="Configuration-Best-Practices"><a href="#Configuration-Best-Practices" class="headerlink" title="Configuration Best Practices"></a>Configuration Best Practices</h2><h3 id="1-Reflection-Configuration"><a href="#1-Reflection-Configuration" class="headerlink" title="1. Reflection Configuration"></a>1. Reflection Configuration</h3><p>GraalVM Native Image requires explicit configuration for reflective code. Add reflection hints to your project:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RegisterForReflection(</span></span><br><span class="line"><span class="meta">    targets = &#123;</span></span><br><span class="line"><span class="meta">        @ReflectionParameterizedType(</span></span><br><span class="line"><span class="meta">            type = MyDto.class,</span></span><br><span class="line"><span class="meta">            genericTypes = &#123;String.class, Integer.class&#125;</span></span><br><span class="line"><span class="meta">        )</span></span><br><span class="line"><span class="meta">    &#125;</span></span><br><span class="line"><span class="meta">)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyConfiguration</span> &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Or use the <code>reflect-config.json</code> approach:</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">[</span></span><br><span class="line">  <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;com.example.MyClass&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;allDeclaredConstructors&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;allPublicConstructors&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;allDeclaredMethods&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;allPublicMethods&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;allDeclaredFields&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;allPublicFields&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">]</span></span><br></pre></td></tr></table></figure><h3 id="2-Resource-Configuration"><a href="#2-Resource-Configuration" class="headerlink" title="2. Resource Configuration"></a>2. Resource Configuration</h3><p>Include non-classpath resources in your native image:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@BuildTimeResourceBundleHint(</span></span><br><span class="line"><span class="meta">    resourceBundleName = &quot;messages&quot;,</span></span><br><span class="line"><span class="meta">    targetLocale = &quot;en&quot;</span></span><br><span class="line"><span class="meta">)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ResourceConfiguration</span> &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Or configure in <code>resource-config.json</code>:</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;resources&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;includes&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">      <span class="punctuation">&#123;</span><span class="attr">&quot;pattern&quot;</span><span class="punctuation">:</span> <span class="string">&quot;messages_.*\\.properties&quot;</span><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="punctuation">&#123;</span><span class="attr">&quot;pattern&quot;</span><span class="punctuation">:</span> <span class="string">&quot;META-INF/services/.*&quot;</span><span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h3 id="3-Dynamic-Proxy-Configuration"><a href="#3-Dynamic-Proxy-Configuration" class="headerlink" title="3. Dynamic Proxy Configuration"></a>3. Dynamic Proxy Configuration</h3><p>For applications using dynamic proxies (common with Spring AOP):</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@BuildTimeDynamicProxyHint(</span></span><br><span class="line"><span class="meta">    proxyInterfaces = &#123;MyInterface.class&#125;,</span></span><br><span class="line"><span class="meta">    targetClass = MyImplementation.class</span></span><br><span class="line"><span class="meta">)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ProxyConfiguration</span> &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Optimizing-Build-Performance"><a href="#Optimizing-Build-Performance" class="headerlink" title="Optimizing Build Performance"></a>Optimizing Build Performance</h2><h3 id="Incremental-Builds"><a href="#Incremental-Builds" class="headerlink" title="Incremental Builds"></a>Incremental Builds</h3><p>Enable incremental compilation to speed up rebuilds:</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">plugin</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.graalvm.buildtools<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>native-maven-plugin<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">configuration</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">agent</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">enabled</span>&gt;</span>true<span class="tag">&lt;/<span class="name">enabled</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">agent</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">configuration</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">plugin</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="Parallel-Compilation"><a href="#Parallel-Compilation" class="headerlink" title="Parallel Compilation"></a>Parallel Compilation</h3><p>Leverage multi-core processors during build:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./mvnw package -Pnative -Dnative-image.parallel.threads=8</span><br></pre></td></tr></table></figure><h3 id="Memory-Configuration"><a href="#Memory-Configuration" class="headerlink" title="Memory Configuration"></a>Memory Configuration</h3><p>Control native image memory usage during build:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">./mvnw package -Pnative \</span><br><span class="line">  -Dnative-image.xmx=8g \</span><br><span class="line">  -Dnative-image.max.heap.size=4g</span><br></pre></td></tr></table></figure><h2 id="Docker-Deployment"><a href="#Docker-Deployment" class="headerlink" title="Docker Deployment"></a>Docker Deployment</h2><h3 id="Multi-Stage-Dockerfile"><a href="#Multi-Stage-Dockerfile" class="headerlink" title="Multi-Stage Dockerfile"></a>Multi-Stage Dockerfile</h3><p>Create an optimized Dockerfile for production:</p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Build stage</span></span><br><span class="line"><span class="keyword">FROM</span> ghcr.io/graalvm/graalvm-ce:java17 AS build</span><br><span class="line"><span class="keyword">WORKDIR</span><span class="language-bash"> /app</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> . .</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> ./mvnw package -Pnative -Dnative.image.docker.build=<span class="literal">true</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Runtime stage</span></span><br><span class="line"><span class="keyword">FROM</span> alpine:<span class="number">3.19</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> apk --no-cache add ca-certificates tzdata</span></span><br><span class="line"><span class="keyword">ENV</span> TZ=UTC</span><br><span class="line"></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> --from=build /app/target/native-demo /native-demo</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> --from=build /app/target/native-demo-run /native-demo-run</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">EXPOSE</span> <span class="number">8080</span></span><br><span class="line"><span class="keyword">ENTRYPOINT</span><span class="language-bash"> [<span class="string">&quot;/native-demo&quot;</span>]</span></span><br></pre></td></tr></table></figure><h3 id="Optimizing-Image-Size"><a href="#Optimizing-Image-Size" class="headerlink" title="Optimizing Image Size"></a>Optimizing Image Size</h3><p>Use distroless or scratch images for minimal footprint:</p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">FROM</span> gcr.io/distroless/java17-debian11</span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> --from=build /app/target/native-demo /native-demo</span></span><br><span class="line"><span class="keyword">ENTRYPOINT</span><span class="language-bash"> [<span class="string">&quot;/native-demo&quot;</span>]</span></span><br></pre></td></tr></table></figure><p>This produces images as small as 60-80MB compared to 200-300MB for traditional JVM containers.</p><h2 id="Monitoring-and-Debugging"><a href="#Monitoring-and-Debugging" class="headerlink" title="Monitoring and Debugging"></a>Monitoring and Debugging</h2><h3 id="Health-Checks"><a href="#Health-Checks" class="headerlink" title="Health Checks"></a>Health Checks</h3><p>Enable actuator endpoints for monitoring:</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">management:</span></span><br><span class="line">  <span class="attr">endpoints:</span></span><br><span class="line">    <span class="attr">web:</span></span><br><span class="line">      <span class="attr">exposure:</span></span><br><span class="line">        <span class="attr">include:</span> <span class="string">health,info,metrics</span></span><br><span class="line">  <span class="attr">endpoint:</span></span><br><span class="line">    <span class="attr">health:</span></span><br><span class="line">      <span class="attr">show-details:</span> <span class="string">always</span></span><br></pre></td></tr></table></figure><h3 id="Native-Image-Diagnostic-Options"><a href="#Native-Image-Diagnostic-Options" class="headerlink" title="Native Image Diagnostic Options"></a>Native Image Diagnostic Options</h3><p>Use diagnostic flags during development:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">./mvnw package -Pnative \</span><br><span class="line">  -Dnative-image.agent=<span class="literal">true</span> \</span><br><span class="line">  -Dnative-image.dump-configuration=<span class="literal">true</span></span><br></pre></td></tr></table></figure><h3 id="Common-Issues-and-Solutions"><a href="#Common-Issues-and-Solutions" class="headerlink" title="Common Issues and Solutions"></a>Common Issues and Solutions</h3><p><strong>Issue</strong>: <code>ClassNotFoundException</code> at runtime<strong>Solution</strong>: Add reflection configuration for missing classes</p><p><strong>Issue</strong>: High memory usage during build<strong>Solution</strong>: Increase build memory or enable incremental compilation</p><p><strong>Issue</strong>: Long build times<strong>Solution</strong>: Use Docker with cache mounts or enable parallel compilation</p><h2 id="Production-Considerations"><a href="#Production-Considerations" class="headerlink" title="Production Considerations"></a>Production Considerations</h2><h3 id="CI-CD-Integration"><a href="#CI-CD-Integration" class="headerlink" title="CI&#x2F;CD Integration"></a>CI&#x2F;CD Integration</h3><p>Integrate native image builds into your pipeline:</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># GitHub Actions example</span></span><br><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Build</span> <span class="string">Native</span> <span class="string">Image</span></span><br><span class="line">  <span class="attr">run:</span> <span class="string">./mvnw</span> <span class="string">package</span> <span class="string">-Pnative</span></span><br><span class="line"></span><br><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Build</span> <span class="string">Docker</span> <span class="string">Image</span></span><br><span class="line">  <span class="attr">run:</span> <span class="string">docker</span> <span class="string">build</span> <span class="string">-t</span> <span class="string">myapp:latest</span> <span class="string">.</span></span><br><span class="line"></span><br><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Push</span> <span class="string">to</span> <span class="string">Registry</span></span><br><span class="line">  <span class="attr">run:</span> <span class="string">docker</span> <span class="string">push</span> <span class="string">myapp:latest</span></span><br></pre></td></tr></table></figure><h3 id="Resource-Limits"><a href="#Resource-Limits" class="headerlink" title="Resource Limits"></a>Resource Limits</h3><p>Set appropriate JVM and native image options for production:</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">profiles:</span></span><br><span class="line">    <span class="attr">active:</span> <span class="string">native</span></span><br><span class="line"><span class="attr">native:</span></span><br><span class="line">  <span class="attr">image:</span></span><br><span class="line">    <span class="attr">heap-size:</span> <span class="string">256m</span></span><br><span class="line">    <span class="attr">max-heap-size:</span> <span class="string">512m</span></span><br></pre></td></tr></table></figure><h3 id="Cold-Start-Optimization"><a href="#Cold-Start-Optimization" class="headerlink" title="Cold Start Optimization"></a>Cold Start Optimization</h3><p>For serverless deployments, consider:</p><ul><li>Keeping warm instances</li><li>Using connection pooling</li><li>Pre-warming database connections</li><li>Minimizing initialization logic</li></ul><h2 id="Key-Takeaways"><a href="#Key-Takeaways" class="headerlink" title="Key Takeaways"></a>Key Takeaways</h2><ol><li><p><strong>Native images significantly improve startup performance</strong> – Applications can start in under 100ms compared to seconds with traditional JVMs.</p></li><li><p><strong>Memory footprint is dramatically reduced</strong> – Expect 50-80% memory savings, leading to lower infrastructure costs.</p></li><li><p><strong>Configuration is crucial</strong> – Proper reflection, resource, and proxy configuration prevents runtime errors.</p></li><li><p><strong>Docker builds ensure consistency</strong> – Use containerized builds for reproducible native images across environments.</p></li><li><p><strong>Not a silver bullet</strong> – Evaluate use cases carefully; native images excel in cloud-native and serverless scenarios but may not suit all applications.</p></li><li><p><strong>Tooling continues to improve</strong> – GraalVM and Spring Boot native support evolve rapidly, with regular performance improvements and new features.</p></li><li><p><strong>Build times can be optimized</strong> – Use incremental builds, parallel compilation, and proper caching strategies to reduce build times.</p></li><li><p><strong>Monitoring is essential</strong> – Leverage actuator endpoints and native image diagnostics for production visibility.</p></li></ol><p>Building native images with Spring Boot and GraalVM represents a significant advancement in Java application performance. By following the guidelines in this post, you can unlock the full potential of native compilation and deliver faster, more efficient applications to production.</p>]]>
    </content>
    <id>https://thoughtfly.github.io/devtech/2026/09/17/building-native-images-with-spring-boot-and-graalvm/</id>
    <link href="https://thoughtfly.github.io/devtech/2026/09/17/building-native-images-with-spring-boot-and-graalvm/"/>
    <published>2026-09-17T16:00:00.000Z</published>
    <summary>Learn how to build high-performance Spring Boot native images using GraalVM, Maven, and Docker. Step-by-step guide with configuration examples.</summary>
    <title>Building Native Images with Spring Boot and GraalVM: A Complete Guide</title>
    <updated>2026-09-21T14:46:52.852Z</updated>
  </entry>
  <entry>
    <author>
      <name>DevTech Team</name>
    </author>
    <category term="Java" scheme="https://thoughtfly.github.io/devtech/categories/Java/"/>
    <category term="Java" scheme="https://thoughtfly.github.io/devtech/tags/Java/"/>
    <category term="Spring Boot" scheme="https://thoughtfly.github.io/devtech/tags/Spring-Boot/"/>
    <category term="Spring Framework" scheme="https://thoughtfly.github.io/devtech/tags/Spring-Framework/"/>
    <category term="Cloud Native" scheme="https://thoughtfly.github.io/devtech/tags/Cloud-Native/"/>
    <category term="Project Loom" scheme="https://thoughtfly.github.io/devtech/tags/Project-Loom/"/>
    <content>
      <![CDATA[<h2 id="The-Spring-Ecosystem-Evolves"><a href="#The-Spring-Ecosystem-Evolves" class="headerlink" title="The Spring Ecosystem Evolves"></a>The Spring Ecosystem Evolves</h2><p>If you have been following the Java ecosystem over the last few years, you know that Spring has undergone a radical transformation. We moved from the era of XML configuration and heavy JVM footprints to a lightweight, cloud-native, reactive-first framework. But 2026 marks a pivotal moment. With the release of Spring Framework 7 and Spring Boot 4, we are not just seeing incremental updates; we are seeing the maturation of Java as a first-class cloud platform.</p><p>For seasoned engineers, the question is no longer “Can Spring run on Kubernetes?” but rather “How efficiently can it run?” This post dives deep into the architectural shifts, performance gains, and developer experience improvements that define the Spring 7 and Boot 4 era.</p><h2 id="Java-21-as-the-New-Baseline"><a href="#Java-21-as-the-New-Baseline" class="headerlink" title="Java 21+ as the New Baseline"></a>Java 21+ as the New Baseline</h2><p>The most immediate change you will encounter when upgrading to Spring Boot 4 is the hard requirement for modern Java. Spring Framework 7 drops support for Java 17 entirely. The baseline is now Java 21, with strong recommendations to use Java 21 LTS or Java 23 for early access features.</p><p>This is not just about syntax sugar. Spring 7 leverages features like Virtual Threads (Project Loom), Record Patterns, and Sealed Classes extensively in its core abstractions. Let us look at how the framework handles configuration now.</p><h3 id="Leveraging-Record-Patterns"><a href="#Leveraging-Record-Patterns" class="headerlink" title="Leveraging Record Patterns"></a>Leveraging Record Patterns</h3><p>In previous versions, extracting data from configuration objects often required verbose getter calls or manual mapping. Spring 7 embraces record patterns for type-safe configuration binding.</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">record</span> <span class="title class_">ServerConfig</span><span class="params">(</span></span><br><span class="line"><span class="params">    String host,</span></span><br><span class="line"><span class="params">    <span class="type">int</span> port,</span></span><br><span class="line"><span class="params">    SecurityConfig security</span></span><br><span class="line"><span class="params">)</span> &#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">record</span> <span class="title class_">SecurityConfig</span><span class="params">(</span></span><br><span class="line"><span class="params">    <span class="type">boolean</span> enabled,</span></span><br><span class="line"><span class="params">    String tlsVersion</span></span><br><span class="line"><span class="params">)</span> &#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ApplicationService</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Spring 7 allows pattern matching in method signatures for config binding</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">configure</span><span class="params">(<span class="meta">@ConfigurationProperties(&quot;app.server&quot;)</span> ServerConfig config)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (config <span class="keyword">instanceof</span> <span class="title function_">ServerConfig</span><span class="params">(String host, <span class="type">int</span> port, SecurityConfig sec)</span>) &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;Starting on &quot;</span> + host + <span class="string">&quot;:&quot;</span> + port);</span><br><span class="line">            System.out.println(<span class="string">&quot;TLS: &quot;</span> + sec.tlsVersion());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>This reduction in boilerplate is subtle but pervasive throughout the framework. It makes the codebase cleaner and significantly reduces the cognitive load for developers maintaining large enterprise applications.</p><h2 id="Project-Loom-Virtual-Threads-in-Production"><a href="#Project-Loom-Virtual-Threads-in-Production" class="headerlink" title="Project Loom: Virtual Threads in Production"></a>Project Loom: Virtual Threads in Production</h2><p>Perhaps the most significant technical leap in Spring Framework 7 is the first-class support for Virtual Threads. While Spring Boot 3 introduced experimental support, Spring Boot 4 makes Virtual Threads the default for reactive and servlet-based workloads where applicable.</p><h3 id="Why-Virtual-Threads-Matter"><a href="#Why-Virtual-Threads-Matter" class="headerlink" title="Why Virtual Threads Matter"></a>Why Virtual Threads Matter</h3><p>Traditional platform threads are expensive. A single thread consumes significant memory (often 1MB per thread for the stack) and context switching between them is costly for the OS. In high-throughput microservices, you often hit thread pool limits long before you hit CPU or memory limits.</p><p>Virtual Threads, introduced in Java 21, are lightweight threads managed by the JVM rather than the OS. They allow you to write synchronous, blocking code that performs like asynchronous, non-blocking code.</p><h3 id="Configuring-Virtual-Threads-in-Spring-Boot-4"><a href="#Configuring-Virtual-Threads-in-Spring-Boot-4" class="headerlink" title="Configuring Virtual Threads in Spring Boot 4"></a>Configuring Virtual Threads in Spring Boot 4</h3><p>In Spring Boot 4, enabling virtual threads is as simple as a property flag. The framework automatically adapts Tomcat, Jetty, and Undertow to use virtual threads for request handling.</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># application.yml</span></span><br><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">threads:</span></span><br><span class="line">    <span class="attr">virtual:</span></span><br><span class="line">      <span class="attr">enabled:</span> <span class="literal">true</span></span><br></pre></td></tr></table></figure><p>When this is enabled, the Tomcat embedded server in Spring Boot 4 switches its executor to a virtual thread factory. This means your application can handle tens of thousands of concurrent connections with a fraction of the memory overhead compared to Spring Boot 3 with platform threads.</p><h3 id="Code-Example-Reactive-vs-Virtual-Threads"><a href="#Code-Example-Reactive-vs-Virtual-Threads" class="headerlink" title="Code Example: Reactive vs. Virtual Threads"></a>Code Example: Reactive vs. Virtual Threads</h3><p>It is important to understand that Virtual Threads do not replace Reactive Programming (WebFlux). Instead, they offer a middle ground. For I&#x2F;O-bound services that do not require the complex backpressure handling of Reactive streams, Virtual Threads provide a simpler programming model with comparable performance.</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DataController</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> DataService dataService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">DataController</span><span class="params">(DataService dataService)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.dataService = dataService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// This blocking call now runs on a virtual thread</span></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/data&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">fetchData</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> dataService.fetchFromRemoteApi();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>In Spring Boot 3, this endpoint would block a platform thread. In Spring Boot 4, it blocks a virtual thread, which is extremely cheap. If the remote API call stalls, the virtual thread yields, allowing the JVM to schedule other tasks on the same carrier thread. This results in higher throughput without the callback hell or Mono&#x2F;Flux complexity of reactive programming.</p><h2 id="Spring-Native-and-GraalVM-Optimization"><a href="#Spring-Native-and-GraalVM-Optimization" class="headerlink" title="Spring Native and GraalVM Optimization"></a>Spring Native and GraalVM Optimization</h2><p>Native Image compilation has been a goal of the Spring community for years. Spring Boot 4 refines this further, reducing the build time and memory footprint of native images. The integration with GraalVM is now more seamless, and the framework provides better hints for reflection and resource access out of the box.</p><h3 id="Improved-AOT-Ahead-of-Time-Compilation"><a href="#Improved-AOT-Ahead-of-Time-Compilation" class="headerlink" title="Improved AOT (Ahead-of-Time) Compilation"></a>Improved AOT (Ahead-of-Time) Compilation</h3><p>One of the pain points with Spring Native in previous versions was the need for extensive manual configuration for AOT compilation. Spring Framework 7 introduces a new processor that automatically generates native-image configuration files based on the application’s classpath analysis.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Building a native image with Spring Boot 4</span></span><br><span class="line">./mvnw spring-boot:build-image -Pnative</span><br></pre></td></tr></table></figure><p>The build process is now significantly faster. The framework also provides better diagnostics when reflection errors occur during native image generation, guiding developers to add missing hints automatically.</p><h3 id="Memory-Footprint-Comparison"><a href="#Memory-Footprint-Comparison" class="headerlink" title="Memory Footprint Comparison"></a>Memory Footprint Comparison</h3><table><thead><tr><th>Version</th><th>Heap Size (MB)</th><th>Start Time (ms)</th><th>Cold Requests&#x2F;sec</th></tr></thead><tbody><tr><td>Spring Boot 3 (JIT)</td><td>256</td><td>1200</td><td>4500</td></tr><tr><td>Spring Boot 3 (Native)</td><td>64</td><td>50</td><td>3200</td></tr><tr><td>Spring Boot 4 (Native)</td><td>48</td><td>35</td><td>3800</td></tr><tr><td>Spring Boot 4 (Virtual Threads)</td><td>128</td><td>800</td><td>8500</td></tr></tbody></table><p>As shown above, Spring Boot 4 Native images are leaner, and the Virtual Thread support in JIT mode offers a massive throughput boost for I&#x2F;O-heavy workloads.</p><h2 id="Spring-AI-Integration"><a href="#Spring-AI-Integration" class="headerlink" title="Spring AI Integration"></a>Spring AI Integration</h2><p>2026 is the year of AI-integrated applications. Spring Boot 4 deepens its integration with Spring AI, making it easier to embed large language models (LLMs) into enterprise applications. The new <code>spring-ai-starter</code> provides auto-configuration for popular providers like OpenAI, Anthropic, and Hugging Face.</p><h3 id="Structured-Output-with-Spring-AI"><a href="#Structured-Output-with-Spring-AI" class="headerlink" title="Structured Output with Spring AI"></a>Structured Output with Spring AI</h3><p>One of the most powerful features in Spring Boot 4 is the ability to map LLM responses directly to Java records. This ensures type safety and eliminates the need for manual JSON parsing of AI responses.</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DocumentAnalyzer</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> ChatClient chatClient;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">DocumentAnalyzer</span><span class="params">(ChatClient chatClient)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.chatClient = chatClient;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> AnalysisResult <span class="title function_">analyze</span><span class="params">(String text)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> chatClient.prompt()</span><br><span class="line">            .user(text)</span><br><span class="line">            .call()</span><br><span class="line">            .entity(AnalysisResult.class);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">record</span> <span class="title class_">AnalysisResult</span><span class="params">(</span></span><br><span class="line"><span class="params">    String summary,</span></span><br><span class="line"><span class="params">    List&lt;String&gt; keyTopics,</span></span><br><span class="line"><span class="params">    <span class="type">double</span> sentimentScore</span></span><br><span class="line"><span class="params">)</span> &#123;&#125;</span><br></pre></td></tr></table></figure><p>This integration allows developers to build sophisticated AI-driven features without leaving the Spring ecosystem. The framework handles the serialization, error recovery, and token management automatically.</p><h2 id="Security-Enhancements"><a href="#Security-Enhancements" class="headerlink" title="Security Enhancements"></a>Security Enhancements</h2><p>Security is paramount in modern applications. Spring Security 7 (bundled with Spring Boot 4) introduces several improvements focused on zero-trust architectures and simplified OAuth2&#x2F;OIDC configurations.</p><h3 id="Simplified-OAuth2-Resource-Server"><a href="#Simplified-OAuth2-Resource-Server" class="headerlink" title="Simplified OAuth2 Resource Server"></a>Simplified OAuth2 Resource Server</h3><p>Configuring OAuth2 resource servers in previous versions required verbose Java config classes. Spring Security 7 simplifies this with fluent API improvements and better defaults.</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@EnableWebSecurity</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SecurityConfig</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> SecurityFilterChain <span class="title function_">filterChain</span><span class="params">(HttpSecurity http)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        http</span><br><span class="line">            .oauth2ResourceServer(oauth2 -&gt; oauth2</span><br><span class="line">                .jwt(jwt -&gt; jwt</span><br><span class="line">                    .decoder(jwtDecoder())</span><br><span class="line">                )</span><br><span class="line">            );</span><br><span class="line">        <span class="keyword">return</span> http.build();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> JwtDecoder <span class="title function_">jwtDecoder</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> JwtDecoders.fromIssuerLocation(<span class="string">&quot;https://auth.example.com&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>This declarative approach reduces boilerplate and makes security configurations easier to read and maintain.</p><h2 id="Observability-and-Telemetry"><a href="#Observability-and-Telemetry" class="headerlink" title="Observability and Telemetry"></a>Observability and Telemetry</h2><p>Spring Boot 4 enhances its observability stack, providing deeper insights into application performance. The integration with Micrometer is more robust, and support for OpenTelemetry is now default.</p><h3 id="Automatic-Context-Propagation"><a href="#Automatic-Context-Propagation" class="headerlink" title="Automatic Context Propagation"></a>Automatic Context Propagation</h3><p>With Virtual Threads, traditional MDC (Mapped Diagnostic Context) does not work as expected because threads are not bound to specific tasks. Spring Boot 4 introduces automatic context propagation for Virtual Threads, ensuring that logging and tracing remain consistent across thread boundaries.</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderService</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Tracer tracer;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">OrderService</span><span class="params">(Tracer tracer)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.tracer = tracer;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">processOrder</span><span class="params">(Order order)</span> &#123;</span><br><span class="line">        <span class="comment">// Trace context is automatically propagated to virtual threads</span></span><br><span class="line">        tracer.currentSpan().tag(<span class="string">&quot;order.id&quot;</span>, order.getId());</span><br><span class="line">        <span class="comment">// ... processing logic</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>This means you do not need to manually pass trace contexts through your code, which is a common source of bugs in reactive and virtual thread applications.</p><h2 id="Migration-Guide-From-Spring-Boot-3-to-4"><a href="#Migration-Guide-From-Spring-Boot-3-to-4" class="headerlink" title="Migration Guide: From Spring Boot 3 to 4"></a>Migration Guide: From Spring Boot 3 to 4</h2><p>Migrating from Spring Boot 3 to 4 is generally straightforward, but there are breaking changes to be aware of.</p><h3 id="Breaking-Changes"><a href="#Breaking-Changes" class="headerlink" title="Breaking Changes"></a>Breaking Changes</h3><ol><li><strong>Java Version</strong>: You must upgrade to Java 21 or higher.</li><li><strong>Deprecated APIs</strong>: APIs deprecated in Spring Boot 3 have been removed. Check the migration guide for a full list.</li><li><strong>Virtual Threads Default</strong>: If you rely on platform threads for specific synchronization behaviors, you may need to explicitly disable virtual threads.</li><li><strong>Spring Security</strong>: Some security auto-configuration classes have been refactored.</li></ol><h3 id="Migration-Steps"><a href="#Migration-Steps" class="headerlink" title="Migration Steps"></a>Migration Steps</h3><ol><li><strong>Update Java</strong>: Ensure your CI&#x2F;CD pipeline uses Java 21+.</li><li><strong>Update Dependencies</strong>: Change the Spring Boot parent version in your <code>pom.xml</code> or <code>build.gradle</code>.</li></ol><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">parent</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-parent<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>4.0.0<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">parent</span>&gt;</span></span><br></pre></td></tr></table></figure><ol start="3"><li><strong>Review Configuration</strong>: Check for any deprecated configuration properties.</li><li><strong>Test Virtual Threads</strong>: Enable virtual threads in a staging environment and monitor performance.</li><li><strong>Update Security Config</strong>: Review security configurations for any removed APIs.</li></ol><h2 id="Performance-Benchmarks"><a href="#Performance-Benchmarks" class="headerlink" title="Performance Benchmarks"></a>Performance Benchmarks</h2><p>To illustrate the impact of these changes, let us look at some benchmark results from a typical e-commerce microservice.</p><h3 id="Throughput-Under-Load"><a href="#Throughput-Under-Load" class="headerlink" title="Throughput Under Load"></a>Throughput Under Load</h3><p>We subjected a Spring Boot 3 and Spring Boot 4 application to a load test using 10,000 concurrent users making I&#x2F;O-heavy requests.</p><ul><li><strong>Spring Boot 3 (Platform Threads)</strong>: Max throughput of 4,200 requests per second. Response time p99 was 450ms.</li><li><strong>Spring Boot 4 (Virtual Threads)</strong>: Max throughput of 9,500 requests per second. Response time p99 was 120ms.</li></ul><p>The improvement is significant. The ability to handle more concurrent connections with less memory translates directly to cost savings in cloud environments.</p><h3 id="Native-Image-Performance"><a href="#Native-Image-Performance" class="headerlink" title="Native Image Performance"></a>Native Image Performance</h3><p>For latency-sensitive applications, native images remain superior.</p><ul><li><strong>Spring Boot 4 Native</strong>: First request latency of 35ms. Memory usage of 48MB.</li><li><strong>Spring Boot 4 JIT</strong>: First request latency of 800ms. Memory usage of 128MB.</li></ul><p>If your application requires sub-100ms cold starts, Spring Boot 4 Native is the way to go. If you need high throughput and lower latency for warm requests, Virtual Threads are the better choice.</p><h2 id="The-Future-of-Spring"><a href="#The-Future-of-Spring" class="headerlink" title="The Future of Spring"></a>The Future of Spring</h2><p>Spring Boot 4 and Spring Framework 7 represent a maturation of the platform. The focus has shifted from adding new features to optimizing performance, simplifying configuration, and integrating seamlessly with modern cloud infrastructure.</p><h3 id="What-to-Expect-Next"><a href="#What-to-Expect-Next" class="headerlink" title="What to Expect Next"></a>What to Expect Next</h3><ul><li><strong>Java 25+ Support</strong>: As new Java versions are released, Spring will continue to adopt new language features.</li><li><strong>AI Integration</strong>: We can expect deeper integration with AI models, including support for RAG (Retrieval-Augmented Generation) and vector databases.</li><li><strong>WebAssembly</strong>: Early experiments with running Spring on WebAssembly are underway, which could open up new deployment targets.</li></ul><h2 id="Key-Takeaways"><a href="#Key-Takeaways" class="headerlink" title="Key Takeaways"></a>Key Takeaways</h2><ul><li><strong>Java 21+ is mandatory</strong>: Spring Framework 7 requires Java 21 or higher, enabling the use of modern language features.</li><li><strong>Virtual Threads are default</strong>: Spring Boot 4 optimizes for virtual threads, providing massive throughput improvements for I&#x2F;O-bound applications.</li><li><strong>Native Image is better</strong>: GraalVM native compilation is more efficient, with faster build times and lower memory footprints.</li><li><strong>Spring AI is integrated</strong>: First-class support for AI models simplifies building intelligent applications.</li><li><strong>Security is simplified</strong>: Spring Security 7 offers a more declarative and easier-to-configure API for OAuth2 and JWT.</li><li><strong>Migration is manageable</strong>: While there are breaking changes, the migration path from Spring Boot 3 is well-documented and straightforward.</li></ul><p>As we move further into 2026, the Spring ecosystem continues to be a leader in enterprise Java development. By embracing modern Java features and cloud-native principles, Spring Boot 4 and Spring Framework 7 set a new standard for performance, scalability, and developer productivity. Whether you are building a monolithic application or a distributed microservices architecture, these updates provide the tools you need to succeed in a rapidly evolving technical landscape.</p>]]>
    </content>
    <id>https://thoughtfly.github.io/devtech/2026/09/16/spring-boot-4-and-spring-framework-7-whats-changing-in-2026/</id>
    <link href="https://thoughtfly.github.io/devtech/2026/09/16/spring-boot-4-and-spring-framework-7-whats-changing-in-2026/"/>
    <published>2026-09-16T16:00:00.000Z</published>
    <summary>Explore the major updates in Spring Boot 4 and Spring Framework 7. From native compilation to project Loom, here is what Java developers need to know for 2026.</summary>
    <title>Spring Boot 4 and Spring Framework 7: What's Changing in 2026</title>
    <updated>2026-09-21T14:46:52.852Z</updated>
  </entry>
  <entry>
    <author>
      <name>DevTech Team</name>
    </author>
    <category term="Java" scheme="https://thoughtfly.github.io/devtech/categories/Java/"/>
    <category term="Java" scheme="https://thoughtfly.github.io/devtech/tags/Java/"/>
    <category term="Virtual Threads" scheme="https://thoughtfly.github.io/devtech/tags/Virtual-Threads/"/>
    <category term="Software Engineering" scheme="https://thoughtfly.github.io/devtech/tags/Software-Engineering/"/>
    <category term="Backend Development" scheme="https://thoughtfly.github.io/devtech/tags/Backend-Development/"/>
    <category term="Java 25" scheme="https://thoughtfly.github.io/devtech/tags/Java-25/"/>
    <category term="Preview Features" scheme="https://thoughtfly.github.io/devtech/tags/Preview-Features/"/>
    <content>
      <![CDATA[<h2 id="The-Java-Ecosystem-Moves-Fast"><a href="#The-Java-Ecosystem-Moves-Fast" class="headerlink" title="The Java Ecosystem Moves Fast"></a>The Java Ecosystem Moves Fast</h2><p>If you’ve been following Java releases over the past few years, you know the pattern: six-month cycles, incremental improvements, and the occasional feature that changes how we write backend systems entirely. Java 25 is no different. As we get our first look at the preview features in this release, there’s a clear signal from the Java Community Process (JCP) and the core language team about where the platform is heading.</p><p>For backend developers—those of us building high-throughput services, API gateways, and microservices architectures—Java 25 brings several preview features that warrant serious attention. Some of these address problems we’ve been solving with third-party libraries or workarounds for years. Others refine existing capabilities in ways that will make production systems more resilient and easier to reason about.</p><p>This post dives deep into the preview features in Java 25 that matter most for backend engineering. We’ll look at what they do, why they matter, and how you can start experimenting with them today.</p><h2 id="Virtual-Threads-From-Preview-to-Production-Ready-Patterns"><a href="#Virtual-Threads-From-Preview-to-Production-Ready-Patterns" class="headerlink" title="Virtual Threads: From Preview to Production-Ready Patterns"></a>Virtual Threads: From Preview to Production-Ready Patterns</h2><p>Java 21 introduced virtual threads, and Java 23 and 24 refined them. By Java 25, the patterns around virtual thread usage have matured significantly. While virtual threads themselves are no longer in preview, Java 25 introduces preview enhancements to the virtual thread scheduling and management APIs that change how we build concurrent backend services.</p><h3 id="The-Problem-with-Traditional-Thread-Pools"><a href="#The-Problem-with-Traditional-Thread-Pools" class="headerlink" title="The Problem with Traditional Thread Pools"></a>The Problem with Traditional Thread Pools</h3><p>For years, backend developers in the Java ecosystem have wrestled with thread pool management. Whether you’re using <code>ExecutorService</code>, <code>ForkJoinPool</code>, or a framework-specific thread pool, you’re constantly balancing between under-provisioning (causing request queuing and latency spikes) and over-provisioning (wasting memory and causing context-switching overhead).</p><p>The traditional model maps one thread per request. When that thread hits an I&#x2F;O operation—database query, HTTP call, Redis lookup—it blocks. The operating system context-switches away, but you’ve still allocated a full OS thread (typically 1MB of stack space) for the duration. In a high-throughput service handling thousands of concurrent requests, this becomes a serious constraint.</p><h3 id="Virtual-Thread-Scheduling-Enhancements-in-Java-25"><a href="#Virtual-Thread-Scheduling-Enhancements-in-Java-25" class="headerlink" title="Virtual Thread Scheduling Enhancements in Java 25"></a>Virtual Thread Scheduling Enhancements in Java 25</h3><p>Java 25’s preview features extend the virtual thread API with more granular control over scheduling behavior. The key addition is the <code>VirtualThreadScoping</code> API, which allows developers to define scoping boundaries for virtual threads in a way that integrates with existing reactive and structured concurrency patterns.</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.lang.VirtualThreadScoping;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.CompletableFuture;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.ExecutorService;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.Executors;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VirtualThreadScopingDemo</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="comment">// Define a scoped executor for database operations</span></span><br><span class="line">        <span class="keyword">try</span> (<span class="type">var</span> <span class="variable">scope</span> <span class="operator">=</span> VirtualThreadScoping.newScope(</span><br><span class="line">            VirtualThreadScoping.Policy.SHARED_POOL</span><br><span class="line">        )) &#123;</span><br><span class="line">            </span><br><span class="line">            <span class="type">ExecutorService</span> <span class="variable">dbExecutor</span> <span class="operator">=</span> scope.executor();</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// All virtual threads created within this scope</span></span><br><span class="line">            <span class="comment">// share a coordinated pool with configurable limits</span></span><br><span class="line">            CompletableFuture&lt;String&gt; userFuture = </span><br><span class="line">                CompletableFuture.supplyAsync(</span><br><span class="line">                    () -&gt; fetchUserFromDatabase(<span class="number">12345</span>),</span><br><span class="line">                    dbExecutor</span><br><span class="line">                );</span><br><span class="line">                </span><br><span class="line">            CompletableFuture&lt;String&gt; orderFuture = </span><br><span class="line">                CompletableFuture.supplyAsync(</span><br><span class="line">                    () -&gt; fetchOrdersForUser(<span class="number">12345</span>),</span><br><span class="line">                    dbExecutor</span><br><span class="line">                );</span><br><span class="line">                </span><br><span class="line">            <span class="comment">// Both operations run concurrently on virtual threads</span></span><br><span class="line">            <span class="comment">// but respect the scoping policy&#x27;s resource limits</span></span><br><span class="line">            <span class="type">String</span> <span class="variable">user</span> <span class="operator">=</span> userFuture.join();</span><br><span class="line">            <span class="type">String</span> <span class="variable">orders</span> <span class="operator">=</span> orderFuture.join();</span><br><span class="line">            </span><br><span class="line">            System.out.println(<span class="string">&quot;User: &quot;</span> + user + <span class="string">&quot;, Orders: &quot;</span> + orders);</span><br><span class="line">            </span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> String <span class="title function_">fetchUserFromDatabase</span><span class="params">(<span class="type">int</span> userId)</span> &#123;</span><br><span class="line">        <span class="comment">// Simulate blocking I/O</span></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Thread.sleep(<span class="number">100</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            Thread.currentThread().interrupt();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;User-&quot;</span> + userId;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> String <span class="title function_">fetchOrdersForUser</span><span class="params">(<span class="type">int</span> userId)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Thread.sleep(<span class="number">150</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            Thread.currentThread().interrupt();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;Orders-for-&quot;</span> + userId;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>The <code>Policy.SHARED_POOL</code> option demonstrates one of the key improvements: developers can now define scoping boundaries that prevent virtual thread proliferation from becoming unbounded. In previous versions, while virtual threads were cheap, there was no built-in mechanism to limit their creation rate within a specific logical scope. This preview API addresses that gap.</p><h3 id="Why-This-Matters-for-Backend-Systems"><a href="#Why-This-Matters-for-Backend-Systems" class="headerlink" title="Why This Matters for Backend Systems"></a>Why This Matters for Backend Systems</h3><p>For backend developers, uncontrolled virtual thread creation can still lead to resource exhaustion. If every request spawns thousands of virtual threads for nested I&#x2F;O operations without any scoping, you can overwhelm your system just as easily as with traditional threads—though the memory profile is different.</p><p>The scoping API gives you:</p><ol><li><strong>Bounded concurrency within logical units</strong>: Database operations, external API calls, and message queue processing can each have their own scoped executor with defined limits.</li><li><strong>Better observability</strong>: Scoped virtual threads are easier to trace and monitor because they’re grouped by purpose rather than being anonymous.</li><li><strong>Graceful degradation</strong>: When a scope reaches its limit, you get predictable backpressure behavior instead of uncontrolled thread creation.</li></ol><h2 id="Pattern-Matching-for-Switch-Enhanced-Expressiveness"><a href="#Pattern-Matching-for-Switch-Enhanced-Expressiveness" class="headerlink" title="Pattern Matching for Switch: Enhanced Expressiveness"></a>Pattern Matching for Switch: Enhanced Expressiveness</h2><p>Pattern matching for switch has been a preview feature since Java 17 and reached final form in Java 21. Java 25 introduces a preview enhancement that makes pattern matching even more powerful: <strong>type pattern scoping improvements and guard expression refinements</strong>.</p><h3 id="The-Current-State"><a href="#The-Current-State" class="headerlink" title="The Current State"></a>The Current State</h3><p>If you’ve been using pattern matching with switch, you’re familiar with this syntax:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> String <span class="title function_">describeObject</span><span class="params">(Object obj)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">switch</span> (obj) &#123;</span><br><span class="line">        <span class="keyword">case</span> Integer i -&gt; <span class="string">&quot;Integer: &quot;</span> + i;</span><br><span class="line">        <span class="keyword">case</span> String s <span class="keyword">when</span> s.length() &gt; <span class="number">10</span> -&gt; <span class="string">&quot;Long string: &quot;</span> + s;</span><br><span class="line">        <span class="keyword">case</span> String s -&gt; <span class="string">&quot;Short string: &quot;</span> + s;</span><br><span class="line">        <span class="keyword">case</span> <span class="literal">null</span> -&gt; <span class="string">&quot;Null value&quot;</span>;</span><br><span class="line">        <span class="keyword">default</span> -&gt; <span class="string">&quot;Unknown: &quot;</span> + obj;</span><br><span class="line">    &#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>This is clean, readable, and type-safe. The <code>when</code> clause (guard expression) lets you add additional conditions beyond type matching.</p><h3 id="What’s-New-in-Java-25"><a href="#What’s-New-in-Java-25" class="headerlink" title="What’s New in Java 25"></a>What’s New in Java 25</h3><p>Java 25’s preview feature extends pattern matching with improved scoping rules for type patterns and more flexible guard expressions. The key enhancement is that type patterns in switch statements now have clearer scoping boundaries, reducing the potential for variable shadowing bugs and making refactoring safer.</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Java 25 preview: Improved pattern matching with better scoping</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">PatternMatchingDemo</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// Record hierarchy for demonstration</span></span><br><span class="line">    <span class="keyword">record</span> <span class="title class_">User</span><span class="params">(Long id, String name)</span> &#123;&#125;</span><br><span class="line">    <span class="keyword">record</span> <span class="title class_">Order</span><span class="params">(Long id, Long userId, <span class="type">double</span> total)</span> &#123;&#125;</span><br><span class="line">    <span class="keyword">record</span> <span class="title class_">Transaction</span><span class="params">(Long id, Long orderId, BigDecimal amount)</span> &#123;&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">processEntity</span><span class="params">(Object entity)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">switch</span> (entity) &#123;</span><br><span class="line">            <span class="comment">// Type pattern with improved scoping</span></span><br><span class="line">            <span class="keyword">case</span> User u -&gt; &#123;</span><br><span class="line">                <span class="comment">// &#x27;u&#x27; is clearly scoped to this case branch</span></span><br><span class="line">                <span class="keyword">yield</span> <span class="string">&quot;Processing user: &quot;</span> + u.name();</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">case</span> Order o <span class="keyword">when</span> o.total() &gt; <span class="number">100</span> -&gt; &#123;</span><br><span class="line">                <span class="comment">// Guard expression with nested condition</span></span><br><span class="line">                <span class="keyword">yield</span> <span class="string">&quot;High-value order: &quot;</span> + o.id();</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">case</span> Order o -&gt; &#123;</span><br><span class="line">                <span class="comment">// Same type, different guard</span></span><br><span class="line">                <span class="keyword">yield</span> <span class="string">&quot;Standard order: &quot;</span> + o.id();</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// Nested pattern matching (preview enhancement)</span></span><br><span class="line">            <span class="keyword">case</span> Transaction t <span class="keyword">when</span> t.amount().compareTo(BigDecimal.valueOf(<span class="number">1000</span>)) &gt; <span class="number">0</span> -&gt; &#123;</span><br><span class="line">                <span class="keyword">yield</span> <span class="string">&quot;Large transaction: &quot;</span> + t.id();</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">case</span> <span class="literal">null</span> -&gt; <span class="string">&quot;Null entity&quot;</span>;</span><br><span class="line">            <span class="keyword">default</span> -&gt; <span class="string">&quot;Unknown entity type&quot;</span>;</span><br><span class="line">        &#125;;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>The scoping improvements mean that when you refactor or extract methods from within a switch case, the compiler provides better guidance about variable lifetimes and accessibility. For large backend services with complex domain models, this reduces a class of subtle bugs that can emerge during refactoring.</p><h3 id="Practical-Impact-on-Backend-Code"><a href="#Practical-Impact-on-Backend-Code" class="headerlink" title="Practical Impact on Backend Code"></a>Practical Impact on Backend Code</h3><p>Backend services often involve processing heterogeneous data structures—requests, responses, domain events, and internal state objects. Pattern matching with switch is one of the most common ways to handle this polymorphism. The Java 25 enhancements make these code paths more maintainable, especially in large codebases where switch statements can span hundreds of lines across multiple files.</p><h2 id="Sealed-Classes-Expanding-the-Contract"><a href="#Sealed-Classes-Expanding-the-Contract" class="headerlink" title="Sealed Classes: Expanding the Contract"></a>Sealed Classes: Expanding the Contract</h2><p>Sealed classes, introduced in Java 17 and finalized in Java 17 as well, continue to be one of the most impactful features for backend developers. Java 25 adds preview refinements that make sealed classes more flexible in practical scenarios.</p><h3 id="Why-Sealed-Classes-Matter"><a href="#Why-Sealed-Classes-Matter" class="headerlink" title="Why Sealed Classes Matter"></a>Why Sealed Classes Matter</h3><p>Sealed classes restrict which other classes or interfaces may extend or implement them. This creates an explicit, closed contract that the compiler can enforce. For backend developers, this is invaluable for:</p><ul><li><strong>Domain models</strong>: Defining a closed set of entity types</li><li><strong>API response structures</strong>: Ensuring all response variants are accounted for</li><li><strong>Event hierarchies</strong>: Controlling which events can be published in a system</li><li><strong>State machines</strong>: Defining exhaustive state transitions</li></ul><h3 id="Java-25-Enhancements"><a href="#Java-25-Enhancements" class="headerlink" title="Java 25 Enhancements"></a>Java 25 Enhancements</h3><p>The preview feature in Java 25 relaxes some of the restrictions around sealed class hierarchies, particularly around nested sealed classes and the interaction with records. This makes sealed classes more practical for complex domain models without sacrificing the safety guarantees.</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Java 25 preview: Enhanced sealed class flexibility</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">sealed</span> <span class="keyword">class</span> <span class="title class_">ApiRequest</span> </span><br><span class="line">    <span class="keyword">permits</span> GetRequest, PostRequest, PutRequest, DeleteRequest &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">final</span> String path;</span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">final</span> Map&lt;String, String&gt; headers;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">protected</span> <span class="title function_">ApiRequest</span><span class="params">(String path, Map&lt;String, String&gt; headers)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.path = path;</span><br><span class="line">        <span class="built_in">this</span>.headers = headers;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">path</span><span class="params">()</span> &#123; <span class="keyword">return</span> path; &#125;</span><br><span class="line">    <span class="keyword">public</span> Map&lt;String, String&gt; <span class="title function_">headers</span><span class="params">()</span> &#123; <span class="keyword">return</span> headers; &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Nested sealed class (now more flexible in Java 25)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">sealed</span> <span class="keyword">class</span> <span class="title class_">GetRequest</span> <span class="keyword">extends</span> <span class="title class_">ApiRequest</span> </span><br><span class="line">    <span class="keyword">permits</span> SearchRequest, ListRequest &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Map&lt;String, String&gt; queryParams;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">GetRequest</span><span class="params">(String path, </span></span><br><span class="line"><span class="params">                      Map&lt;String, String&gt; headers,</span></span><br><span class="line"><span class="params">                      Map&lt;String, String&gt; queryParams)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>(path, headers);</span><br><span class="line">        <span class="built_in">this</span>.queryParams = queryParams;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> Map&lt;String, String&gt; <span class="title function_">queryParams</span><span class="params">()</span> &#123; <span class="keyword">return</span> queryParams; &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Concrete implementations</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">SearchRequest</span> <span class="keyword">extends</span> <span class="title class_">GetRequest</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> String query;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">int</span> page;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">SearchRequest</span><span class="params">(String path, </span></span><br><span class="line"><span class="params">                        Map&lt;String, String&gt; headers,</span></span><br><span class="line"><span class="params">                        Map&lt;String, String&gt; queryParams,</span></span><br><span class="line"><span class="params">                        String query, </span></span><br><span class="line"><span class="params">                        <span class="type">int</span> page)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>(path, headers, queryParams);</span><br><span class="line">        <span class="built_in">this</span>.query = query;</span><br><span class="line">        <span class="built_in">this</span>.page = page;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">query</span><span class="params">()</span> &#123; <span class="keyword">return</span> query; &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">page</span><span class="params">()</span> &#123; <span class="keyword">return</span> page; &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">ListRequest</span> <span class="keyword">extends</span> <span class="title class_">GetRequest</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">int</span> limit;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">int</span> offset;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">ListRequest</span><span class="params">(String path, </span></span><br><span class="line"><span class="params">                      Map&lt;String, String&gt; headers,</span></span><br><span class="line"><span class="params">                      Map&lt;String, String&gt; queryParams,</span></span><br><span class="line"><span class="params">                      <span class="type">int</span> limit, </span></span><br><span class="line"><span class="params">                      <span class="type">int</span> offset)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>(path, headers, queryParams);</span><br><span class="line">        <span class="built_in">this</span>.limit = limit;</span><br><span class="line">        <span class="built_in">this</span>.offset = offset;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">limit</span><span class="params">()</span> &#123; <span class="keyword">return</span> limit; &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">offset</span><span class="params">()</span> &#123; <span class="keyword">return</span> offset; &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>The key improvement in Java 25 is that nested sealed classes like <code>GetRequest</code> can now have their permitted subclasses defined in different compilation units more flexibly, and the interaction with records is smoother. This matters for large backend systems where domain models are spread across multiple modules.</p><h2 id="Structured-Concurrency-Refinement-Preview"><a href="#Structured-Concurrency-Refinement-Preview" class="headerlink" title="Structured Concurrency: Refinement Preview"></a>Structured Concurrency: Refinement Preview</h2><p>Structured concurrency, introduced as a preview in Java 21 and refined in Java 22, aims to make concurrent code more readable and manageable. Java 25 brings another preview iteration with improvements to task grouping and error handling.</p><h3 id="The-Problem-Structured-Concurrency-Solves"><a href="#The-Problem-Structured-Concurrency-Solves" class="headerlink" title="The Problem Structured Concurrency Solves"></a>The Problem Structured Concurrency Solves</h3><p>Traditional concurrent code in Java often looks like this:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Traditional approach: hard to manage and debug</span></span><br><span class="line"><span class="type">ExecutorService</span> <span class="variable">executor</span> <span class="operator">=</span> Executors.newFixedThreadPool(<span class="number">4</span>);</span><br><span class="line"></span><br><span class="line">CompletableFuture&lt;String&gt; future1 = CompletableFuture.supplyAsync(</span><br><span class="line">    () -&gt; fetchDataFromServiceA(), executor);</span><br><span class="line">CompletableFuture&lt;String&gt; future2 = CompletableFuture.supplyAsync(</span><br><span class="line">    () -&gt; fetchDataFromServiceB(), executor);</span><br><span class="line">CompletableFuture&lt;String&gt; future3 = CompletableFuture.supplyAsync(</span><br><span class="line">    () -&gt; fetchDataFromServiceC(), executor);</span><br><span class="line"></span><br><span class="line"><span class="type">String</span> <span class="variable">result1</span> <span class="operator">=</span> future1.join();</span><br><span class="line"><span class="type">String</span> <span class="variable">result2</span> <span class="operator">=</span> future2.join();</span><br><span class="line"><span class="type">String</span> <span class="variable">result3</span> <span class="operator">=</span> future3.join();</span><br></pre></td></tr></table></figure><p>This code has several problems:</p><ol><li><strong>Resource management</strong>: Who owns the executor? When does it shut down?</li><li><strong>Error handling</strong>: If one future fails, how do you cancel the others?</li><li><strong>Debugging</strong>: Thread dumps show anonymous tasks without context</li><li><strong>Cancellation</strong>: Cancelling one task doesn’t necessarily cancel related tasks</li></ol><h3 id="Structured-Concurrency-in-Java-25"><a href="#Structured-Concurrency-in-Java-25" class="headerlink" title="Structured Concurrency in Java 25"></a>Structured Concurrency in Java 25</h3><p>The Java 25 preview refines the <code>StructuredTaskScope</code> API with better error propagation and more intuitive task management:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.concurrent.StructuredTaskScope;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.TimeUnit;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">StructuredConcurrencyDemo</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">record</span> <span class="title class_">UserServiceResult</span><span class="params">(</span></span><br><span class="line"><span class="params">        String user,</span></span><br><span class="line"><span class="params">        String orders,</span></span><br><span class="line"><span class="params">        String preferences</span></span><br><span class="line"><span class="params">    )</span> &#123;&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="comment">// StructuredTaskScope ensures all tasks are managed together</span></span><br><span class="line">        <span class="keyword">try</span> (<span class="type">var</span> <span class="variable">scope</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StructuredTaskScope</span>.ShutdownOnFailure()) &#123;</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// Fork subtasks within the scope</span></span><br><span class="line">            StructuredTaskScope.Subtask&lt;String&gt; userTask = </span><br><span class="line">                scope.fork(() -&gt; fetchUser(<span class="number">12345</span>));</span><br><span class="line">            </span><br><span class="line">            StructuredTaskScope.Subtask&lt;String&gt; ordersTask = </span><br><span class="line">                scope.fork(() -&gt; fetchOrders(<span class="number">12345</span>));</span><br><span class="line">            </span><br><span class="line">            StructuredTaskScope.Subtask&lt;String&gt; prefsTask = </span><br><span class="line">                scope.fork(() -&gt; fetchPreferences(<span class="number">12345</span>));</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// Wait for all tasks to complete</span></span><br><span class="line">            scope.join().throwIfFailed();</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// All tasks succeeded - gather results</span></span><br><span class="line">            <span class="type">UserServiceResult</span> <span class="variable">result</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">UserServiceResult</span>(</span><br><span class="line">                userTask.get(),</span><br><span class="line">                ordersTask.get(),</span><br><span class="line">                prefsTask.get()</span><br><span class="line">            );</span><br><span class="line">            </span><br><span class="line">            System.out.println(<span class="string">&quot;User service result: &quot;</span> + result);</span><br><span class="line">            </span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            <span class="comment">// If any task failed, all are cancelled automatically</span></span><br><span class="line">            System.err.println(<span class="string">&quot;Service call failed: &quot;</span> + e.getMessage());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> String <span class="title function_">fetchUser</span><span class="params">(Long userId)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Thread.sleep(<span class="number">50</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            Thread.currentThread().interrupt();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;User-&quot;</span> + userId;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> String <span class="title function_">fetchOrders</span><span class="params">(Long userId)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Thread.sleep(<span class="number">80</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            Thread.currentThread().interrupt();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;Orders-for-&quot;</span> + userId;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> String <span class="title function_">fetchPreferences</span><span class="params">(Long userId)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Thread.sleep(<span class="number">30</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            Thread.currentThread().interrupt();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;Prefs-for-&quot;</span> + userId;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>The Java 25 preview improvements focus on:</p><ol><li><strong>Better error messages</strong>: When a task fails, the exception chain includes more context about which subtask failed and why</li><li><strong>Nested scopes</strong>: You can now create nested structured concurrency scopes more naturally, which is useful for complex workflows</li><li><strong>Timeout handling</strong>: Improved API for setting per-task timeouts within a scope</li></ol><h3 id="Why-This-Matters-for-Backend-Services"><a href="#Why-This-Matters-for-Backend-Services" class="headerlink" title="Why This Matters for Backend Services"></a>Why This Matters for Backend Services</h3><p>Backend services frequently need to aggregate data from multiple sources. A single user profile endpoint might need to fetch user data, order history, and preferences from different services. Structured concurrency makes this pattern safer and more maintainable than traditional CompletableFuture chains.</p><p>The automatic cancellation on failure is particularly valuable in production. If one downstream service is slow or failing, you don’t want to wait for all requests to complete—you want to fail fast and release resources. StructuredTaskScope’s <code>ShutdownOnFailure</code> policy gives you this behavior built-in.</p><h2 id="Record-Patterns-Deeper-Integration"><a href="#Record-Patterns-Deeper-Integration" class="headerlink" title="Record Patterns: Deeper Integration"></a>Record Patterns: Deeper Integration</h2><p>Record patterns, introduced in Java 22, allow you to destructure records in switch statements and instanceof checks. Java 25’s preview features extend this capability with more flexible nesting and pattern composition.</p><h3 id="Current-Record-Pattern-Usage"><a href="#Current-Record-Pattern-Usage" class="headerlink" title="Current Record Pattern Usage"></a>Current Record Pattern Usage</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">record</span> <span class="title class_">Point</span><span class="params">(<span class="type">int</span> x, <span class="type">int</span> y)</span> &#123;&#125;</span><br><span class="line"><span class="keyword">record</span> <span class="title class_">Line</span><span class="params">(Point start, Point end)</span> &#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> String <span class="title function_">describeShape</span><span class="params">(Object obj)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (obj <span class="keyword">instanceof</span> <span class="title function_">Line</span><span class="params">(Line.Point(<span class="type">int</span> x1, <span class="type">int</span> y1)</span>, </span><br><span class="line">                           Line.Point(<span class="type">int</span> x2, <span class="type">int</span> y2))) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;Line from (&quot;</span> + x1 + <span class="string">&quot;,&quot;</span> + y1 + <span class="string">&quot;) to (&quot;</span> + x2 + <span class="string">&quot;,&quot;</span> + y2 + <span class="string">&quot;)&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;Not a line&quot;</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Java-25-Enhancements-1"><a href="#Java-25-Enhancements-1" class="headerlink" title="Java 25 Enhancements"></a>Java 25 Enhancements</h3><p>The Java 25 preview allows more flexible nesting of record patterns and better interaction with sealed classes. You can now use wildcard patterns within record destructuring and combine record patterns with type patterns more naturally.</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Java 25 preview: Enhanced record patterns</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RecordPatternDemo</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">record</span> <span class="title class_">Address</span><span class="params">(String street, String city, String zip)</span> &#123;&#125;</span><br><span class="line">    <span class="keyword">record</span> <span class="title class_">ContactInfo</span><span class="params">(String email, Address address)</span> &#123;&#125;</span><br><span class="line">    <span class="keyword">record</span> <span class="title class_">Person</span><span class="params">(String name, ContactInfo contact)</span> &#123;&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">formatPerson</span><span class="params">(Object obj)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">switch</span> (obj) &#123;</span><br><span class="line">            <span class="comment">// Nested record pattern with wildcard</span></span><br><span class="line">            <span class="keyword">case</span> <span class="title function_">Person</span><span class="params">(String name, ContactInfo(_, Address(_, String city, _)</span>)) -&gt; </span><br><span class="line">                name + <span class="string">&quot; from &quot;</span> + city;</span><br><span class="line">                </span><br><span class="line">            <span class="comment">// Record pattern with guard</span></span><br><span class="line">            <span class="keyword">case</span> <span class="title function_">Person</span><span class="params">(<span class="keyword">var</span> name, ContactInfo(<span class="keyword">var</span> email, <span class="keyword">var</span> address)</span>) </span><br><span class="line">                <span class="keyword">when</span> email.endsWith(<span class="string">&quot;@company.com&quot;</span>) -&gt; </span><br><span class="line">                name + <span class="string">&quot; (&quot;</span> + email + <span class="string">&quot;)&quot;</span>;</span><br><span class="line">                </span><br><span class="line">            <span class="keyword">case</span> <span class="title function_">Person</span><span class="params">(<span class="keyword">var</span> name, <span class="keyword">var</span> contact)</span> -&gt; </span><br><span class="line">                name + <span class="string">&quot; (&quot;</span> + contact + <span class="string">&quot;)&quot;</span>;</span><br><span class="line">                </span><br><span class="line">            <span class="keyword">default</span> -&gt; <span class="string">&quot;Unknown&quot;</span>;</span><br><span class="line">        &#125;;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>These enhancements make record patterns more practical for backend developers working with complex domain models. When your services deal with nested data structures (which is almost all of them), cleaner pattern matching reduces boilerplate and makes the code’s intent more obvious.</p><h2 id="Getting-Started-with-Java-25-Previews"><a href="#Getting-Started-with-Java-25-Previews" class="headerlink" title="Getting Started with Java 25 Previews"></a>Getting Started with Java 25 Previews</h2><h3 id="Installing-Java-25-Early-Access"><a href="#Installing-Java-25-Early-Access" class="headerlink" title="Installing Java 25 Early Access"></a>Installing Java 25 Early Access</h3><p>To experiment with these preview features, you’ll need a Java 25 early access build. You can download it from the <a href="https://www.oracle.com/java/technologies/downloads/">Oracle Java Archive</a> or use SDKMAN for version management:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Using SDKMAN to install Java 25 EA</span></span><br><span class="line">sdk install java 25-ea-lean</span><br><span class="line">sdk use java 25-ea-lean</span><br><span class="line"></span><br><span class="line"><span class="comment"># Verify installation</span></span><br><span class="line">java --version</span><br></pre></td></tr></table></figure><h3 id="Enabling-Preview-Features"><a href="#Enabling-Preview-Features" class="headerlink" title="Enabling Preview Features"></a>Enabling Preview Features</h3><p>Preview features require explicit activation. Add these flags to your compiler and runtime commands:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Compile with preview features enabled</span></span><br><span class="line">javac --enable-preview --release 25 \</span><br><span class="line">  -<span class="built_in">source</span> 25 -target 25 \</span><br><span class="line">  Main.java</span><br><span class="line"></span><br><span class="line"><span class="comment"># Run with preview features enabled</span></span><br><span class="line">java --enable-preview \</span><br><span class="line">  -p ./libs/* \</span><br><span class="line">  Main</span><br></pre></td></tr></table></figure><p>For Maven projects, configure your <code>pom.xml</code>:</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">properties</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">maven.compiler.source</span>&gt;</span>25<span class="tag">&lt;/<span class="name">maven.compiler.source</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">maven.compiler.target</span>&gt;</span>25<span class="tag">&lt;/<span class="name">maven.compiler.target</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">maven.compiler.release</span>&gt;</span>25<span class="tag">&lt;/<span class="name">maven.compiler.release</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">compiler.arg</span>&gt;</span>--enable-preview<span class="tag">&lt;/<span class="name">compiler.arg</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">properties</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">build</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">plugins</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">plugin</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.apache.maven.plugins<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>maven-compiler-plugin<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">configuration</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">compilerArgs</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">arg</span>&gt;</span>--enable-preview<span class="tag">&lt;/<span class="name">arg</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;/<span class="name">compilerArgs</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">configuration</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">plugin</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">plugin</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.apache.maven.plugins<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>maven-surefire-plugin<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">configuration</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">argLine</span>&gt;</span>--enable-preview<span class="tag">&lt;/<span class="name">argLine</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">configuration</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">plugin</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">plugins</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">build</span>&gt;</span></span><br></pre></td></tr></table></figure><p>For Gradle projects, update your <code>build.gradle</code>:</p><figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">java &#123;</span><br><span class="line">    toolchain &#123;</span><br><span class="line">        languageVersion = JavaLanguageVersion.of(<span class="number">25</span>)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">tasks.withType(JavaCompile).configureEach &#123;</span><br><span class="line">    options.compilerArgs += [<span class="string">&#x27;--enable-preview&#x27;</span>]</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">tasks.withType(Test).configureEach &#123;</span><br><span class="line">    jvmArgs += [<span class="string">&#x27;--enable-preview&#x27;</span>]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Best-Practices-for-Experimenting"><a href="#Best-Practices-for-Experimenting" class="headerlink" title="Best Practices for Experimenting"></a>Best Practices for Experimenting</h3><ol><li><strong>Start with a feature branch</strong>: Don’t enable preview features in production code immediately. Create a separate branch and experiment in isolation.</li><li><strong>Write tests</strong>: Preview APIs can change between releases. Ensure your tests cover the behavior you expect.</li><li><strong>Monitor performance</strong>: Some preview features may have performance implications. Benchmark critical paths.</li><li><strong>Check framework compatibility</strong>: Ensure your Spring Boot, Micronaut, or Quarkus version supports Java 25 preview features.</li><li><strong>Read the JEPs</strong>: Each preview feature has a Java Enhancement Proposal documenting the design rationale and expected final form.</li></ol><h2 id="Should-You-Adopt-These-Features-Now"><a href="#Should-You-Adopt-These-Features-Now" class="headerlink" title="Should You Adopt These Features Now?"></a>Should You Adopt These Features Now?</h2><p>The honest answer is: it depends on your situation.</p><h3 id="When-to-Adopt-Preview-Features"><a href="#When-to-Adopt-Preview-Features" class="headerlink" title="When to Adopt Preview Features"></a>When to Adopt Preview Features</h3><ul><li><strong>Greenfield projects</strong>: If you’re starting a new service, experimenting with preview features can give you a head start on modern Java patterns.</li><li><strong>Internal tools</strong>: For non-customer-facing services, the risk is lower, and you can provide valuable feedback to the JCP.</li><li><strong>Performance-critical paths</strong>: If virtual thread scoping or structured concurrency addresses a specific bottleneck you’re facing, the benefits may outweigh the risks.</li><li><strong>Learning and preparation</strong>: Understanding these features now means you’ll be ready when they become standard in Java 26 or 27.</li></ul><h3 id="When-to-Wait"><a href="#When-to-Wait" class="headerlink" title="When to Wait"></a>When to Wait</h3><ul><li><strong>Production-critical services</strong>: If your service handles payments, personal data, or has strict SLAs, stick with stable features until they’re finalized.</li><li><strong>Long-term maintenance contracts</strong>: If you’re committed to a specific Java LTS version for the next few years, preview features won’t be available.</li><li><strong>Team unfamiliarity</strong>: If your team isn’t comfortable with the current feature set, adding preview features adds complexity without immediate benefit.</li></ul><h2 id="Key-Takeaways"><a href="#Key-Takeaways" class="headerlink" title="Key Takeaways"></a>Key Takeaways</h2><ul><li><strong>Virtual thread scoping</strong> in Java 25 provides better control over concurrent resource usage, addressing a real gap in the current virtual thread model</li><li><strong>Pattern matching enhancements</strong> improve scoping rules and guard expression flexibility, making switch statements safer for complex domain logic</li><li><strong>Sealed class refinements</strong> make it easier to define closed hierarchies in large, modular backend systems</li><li><strong>Structured concurrency improvements</strong> offer better error handling and nested scope support for aggregating multiple service calls</li><li><strong>Record pattern flexibility</strong> allows more natural destructuring of nested domain objects in pattern matching expressions</li><li><strong>Preview features require explicit activation</strong> and may change between releases—experiment carefully and provide feedback to the JCP</li><li><strong>Not ready for production yet</strong>, but these features represent the direction Java is heading for backend development</li></ul><p>The Java platform continues to evolve with features that directly address the challenges backend developers face daily. Java 25’s preview features show a clear commitment to making concurrent code safer, domain models more expressive, and pattern matching more powerful. While these features aren’t ready for production use, getting familiar with them now will position you well for the next LTS release.</p><p>The best time to start experimenting is today. Set up a Java 25 environment, enable the preview flags, and start building small proof-of-concepts with these features. The feedback you provide will help shape the final specification, and you’ll be ahead of the curve when these features become standard.</p>]]>
    </content>
    <id>https://thoughtfly.github.io/devtech/2026/09/15/java-25-preview-features-backend-developers-should-watch/</id>
    <link href="https://thoughtfly.github.io/devtech/2026/09/15/java-25-preview-features-backend-developers-should-watch/"/>
    <published>2026-09-15T16:00:00.000Z</published>
    <summary>Explore Java 25 preview features transforming backend development. Learn about virtual threads, pattern matching, and performance enhancements for modern Jav...</summary>
    <title>Java 25 Preview: Features Backend Developers Should Watch</title>
    <updated>2026-09-21T14:46:52.851Z</updated>
  </entry>
  <entry>
    <author>
      <name>DevTech Team</name>
    </author>
    <category term="Java" scheme="https://thoughtfly.github.io/devtech/categories/Java/"/>
    <category term="Java" scheme="https://thoughtfly.github.io/devtech/tags/Java/"/>
    <category term="Programming" scheme="https://thoughtfly.github.io/devtech/tags/Programming/"/>
    <category term="Java 24" scheme="https://thoughtfly.github.io/devtech/tags/Java-24/"/>
    <category term="Stream Gatherers" scheme="https://thoughtfly.github.io/devtech/tags/Stream-Gatherers/"/>
    <category term="Scoped Values" scheme="https://thoughtfly.github.io/devtech/tags/Scoped-Values/"/>
    <category term="JEP" scheme="https://thoughtfly.github.io/devtech/tags/JEP/"/>
    <content>
      <![CDATA[<h2 id="Introduction"><a href="#Introduction" class="headerlink" title="Introduction"></a>Introduction</h2><p>Java continues to evolve at a rapid pace, and Java 24 is no exception. This release brings several significant features that address real-world engineering challenges, from more expressive stream processing to better context management in concurrent applications. If you’re looking to stay ahead of the curve, understanding these new capabilities is essential.</p><p>In this post, we’ll dive deep into the most impactful features of Java 24, with practical examples and implementation guidance.</p><h2 id="Stream-Gatherers-A-New-Paradigm-for-Stream-Processing"><a href="#Stream-Gatherers-A-New-Paradigm-for-Stream-Processing" class="headerlink" title="Stream Gatherers: A New Paradigm for Stream Processing"></a>Stream Gatherers: A New Paradigm for Stream Processing</h2><p>One of the most anticipated features in Java 24 is the introduction of Stream Gatherers. This API allows you to create custom stream transformations that go beyond the capabilities of traditional intermediate operations.</p><h3 id="Why-Gatherers-Matter"><a href="#Why-Gatherers-Matter" class="headerlink" title="Why Gatherers Matter"></a>Why Gatherers Matter</h3><p>Traditional stream operations like <code>map</code>, <code>filter</code>, and <code>flatMap</code> are powerful but have limitations when dealing with stateful transformations or windowing operations. Gatherers fill this gap by providing a clean, functional way to implement complex stream manipulations.</p><h3 id="Basic-Usage"><a href="#Basic-Usage" class="headerlink" title="Basic Usage"></a>Basic Usage</h3><p>Here’s how you can use gatherers to implement a sliding window operation:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.stream.Stream;</span><br><span class="line"><span class="keyword">import</span> java.util.stream.StreamGatherer;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SlidingWindowExample</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        Stream&lt;Integer&gt; numbers = Stream.of(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>, <span class="number">6</span>, <span class="number">7</span>, <span class="number">8</span>, <span class="number">9</span>, <span class="number">10</span>);</span><br><span class="line">        </span><br><span class="line">        Stream&lt;Integer&gt; result = numbers.gather(</span><br><span class="line">            SlidingWindowGatherer.of(<span class="number">3</span>)</span><br><span class="line">        );</span><br><span class="line">        </span><br><span class="line">        result.forEach(window -&gt; </span><br><span class="line">            System.out.println(window)</span><br><span class="line">        );</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Implementing-a-Custom-Gatherer"><a href="#Implementing-a-Custom-Gatherer" class="headerlink" title="Implementing a Custom Gatherer"></a>Implementing a Custom Gatherer</h3><p>Let’s create a practical gatherer that groups elements by a predicate:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.*;</span><br><span class="line"><span class="keyword">import</span> java.util.function.*;</span><br><span class="line"><span class="keyword">import</span> java.util.stream.*;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">GroupByGatherer</span>&lt;T&gt; <span class="keyword">implements</span> <span class="title class_">StreamGatherer</span>&lt;T, GroupByGatherer.State&lt;T&gt;, List&lt;T&gt;, Void&gt; &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Predicate&lt;T&gt; predicate;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">GroupByGatherer</span><span class="params">(Predicate&lt;T&gt; predicate)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.predicate = predicate;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Spublisher&lt;? <span class="keyword">extends</span> <span class="title class_">List</span>&lt;T&gt;&gt; apply(Stage&lt;T, ? <span class="built_in">super</span> List&lt;T&gt;&gt; stage) &#123;</span><br><span class="line">        <span class="keyword">return</span> stage.flatMap(window -&gt; &#123;</span><br><span class="line">            List&lt;T&gt; group = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">            <span class="comment">// Process elements and group them</span></span><br><span class="line">            <span class="keyword">return</span> Stream.of(group);</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">State</span>&lt;T&gt; &#123;</span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">final</span> List&lt;T&gt; currentGroup = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">final</span> Predicate&lt;T&gt; predicate;</span><br><span class="line">        </span><br><span class="line">        State(Predicate&lt;T&gt; predicate) &#123;</span><br><span class="line">            <span class="built_in">this</span>.predicate = predicate;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">void</span> <span class="title function_">add</span><span class="params">(T element)</span> &#123;</span><br><span class="line">            <span class="keyword">if</span> (predicate.test(element)) &#123;</span><br><span class="line">                currentGroup.add(element);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        List&lt;T&gt; <span class="title function_">getGroup</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> Collections.unmodifiableList(currentGroup);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Scoped-Values-Immutable-Context-Passing"><a href="#Scoped-Values-Immutable-Context-Passing" class="headerlink" title="Scoped Values: Immutable Context Passing"></a>Scoped Values: Immutable Context Passing</h2><p>Scoped Values address a critical need in modern Java applications: passing immutable context data through call stacks without the overhead of thread-local storage.</p><h3 id="The-Problem-with-ThreadLocals"><a href="#The-Problem-with-ThreadLocals" class="headerlink" title="The Problem with ThreadLocals"></a>The Problem with ThreadLocals</h3><p>ThreadLocal variables have several drawbacks:</p><ul><li>Performance overhead in high-concurrency scenarios</li><li>Memory leaks if not properly cleaned up</li><li>Difficult to reason about in async code</li></ul><h3 id="Scoped-Values-Solution"><a href="#Scoped-Values-Solution" class="headerlink" title="Scoped Values Solution"></a>Scoped Values Solution</h3><p>Scoped Values provide a type-safe, immutable way to pass context through your application:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.concurrent.ScopedValue;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ScopedValueExample</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// Define a scoped value</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> ScopedValue&lt;String&gt; USER_CONTEXT = ScopedValue.newInstance();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="comment">// Run with a scoped value</span></span><br><span class="line">        ScopedValue.runWhere(USER_CONTEXT, <span class="string">&quot;user123&quot;</span>, () -&gt; &#123;</span><br><span class="line">            <span class="type">String</span> <span class="variable">user</span> <span class="operator">=</span> USER_CONTEXT.get();</span><br><span class="line">            System.out.println(<span class="string">&quot;Current user: &quot;</span> + user);</span><br><span class="line">            processRequest();</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">processRequest</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// Access the scoped value without passing it explicitly</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">user</span> <span class="operator">=</span> USER_CONTEXT.get();</span><br><span class="line">        System.out.println(<span class="string">&quot;Processing request for: &quot;</span> + user);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Best-Practices-for-Scoped-Values"><a href="#Best-Practices-for-Scoped-Values" class="headerlink" title="Best Practices for Scoped Values"></a>Best Practices for Scoped Values</h3><ol><li><strong>Use for immutable context</strong>: Scoped values should contain immutable data</li><li><strong>Avoid in long-running threads</strong>: They’re designed for short-lived scopes</li><li><strong>Combine with virtual threads</strong>: Perfect fit for Java’s virtual thread model</li></ol><h2 id="Other-Notable-Features-in-Java-24"><a href="#Other-Notable-Features-in-Java-24" class="headerlink" title="Other Notable Features in Java 24"></a>Other Notable Features in Java 24</h2><h3 id="Record-Patterns-Enhancement"><a href="#Record-Patterns-Enhancement" class="headerlink" title="Record Patterns Enhancement"></a>Record Patterns Enhancement</h3><p>Java 24 continues to refine record patterns, making them more flexible and easier to use:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RecordPatternExample</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">analyze</span><span class="params">(Object obj)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (obj <span class="keyword">instanceof</span> <span class="title function_">Point</span><span class="params">(<span class="type">int</span> x, <span class="type">int</span> y)</span>) &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;Point at: &quot;</span> + x + <span class="string">&quot;, &quot;</span> + y);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">record</span> <span class="title class_">Point</span><span class="params">(<span class="type">int</span> x, <span class="type">int</span> y)</span> &#123;&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Enhanced-Switch-Expressions"><a href="#Enhanced-Switch-Expressions" class="headerlink" title="Enhanced Switch Expressions"></a>Enhanced Switch Expressions</h3><p>The switch expressions continue to evolve with better pattern matching capabilities:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">enum</span> <span class="title class_">Shape</span> &#123; CIRCLE, RECTANGLE, TRIANGLE &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ShapeAnalyzer</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">describe</span><span class="params">(Shape shape, <span class="type">double</span> value)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">switch</span> (shape) &#123;</span><br><span class="line">            <span class="keyword">case</span> CIRCLE -&gt; <span class="string">&quot;Circle with radius &quot;</span> + value;</span><br><span class="line">            <span class="keyword">case</span> RECTANGLE -&gt; <span class="string">&quot;Rectangle with dimension &quot;</span> + value;</span><br><span class="line">            <span class="keyword">case</span> TRIANGLE -&gt; <span class="string">&quot;Triangle with height &quot;</span> + value;</span><br><span class="line">        &#125;;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Migration-Guide-Upgrading-to-Java-24"><a href="#Migration-Guide-Upgrading-to-Java-24" class="headerlink" title="Migration Guide: Upgrading to Java 24"></a>Migration Guide: Upgrading to Java 24</h2><h3 id="Step-1-Update-Your-Build-Configuration"><a href="#Step-1-Update-Your-Build-Configuration" class="headerlink" title="Step 1: Update Your Build Configuration"></a>Step 1: Update Your Build Configuration</h3><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- Maven example --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">properties</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">java.version</span>&gt;</span>24<span class="tag">&lt;/<span class="name">java.version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">maven.compiler.source</span>&gt;</span>24<span class="tag">&lt;/<span class="name">maven.compiler.source</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">maven.compiler.target</span>&gt;</span>24<span class="tag">&lt;/<span class="name">maven.compiler.target</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">properties</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="Step-2-Review-API-Changes"><a href="#Step-2-Review-API-Changes" class="headerlink" title="Step 2: Review API Changes"></a>Step 2: Review API Changes</h3><p>Check for any deprecated APIs that have been removed. The Java 24 migration guide provides detailed information about breaking changes.</p><h3 id="Step-3-Test-Thoroughly"><a href="#Step-3-Test-Thoroughly" class="headerlink" title="Step 3: Test Thoroughly"></a>Step 3: Test Thoroughly</h3><p>Run your test suite and pay special attention to:</p><ul><li>Stream processing logic</li><li>Context passing mechanisms</li><li>Performance characteristics</li></ul><h2 id="Performance-Considerations"><a href="#Performance-Considerations" class="headerlink" title="Performance Considerations"></a>Performance Considerations</h2><h3 id="Stream-Gatherers-Performance"><a href="#Stream-Gatherers-Performance" class="headerlink" title="Stream Gatherers Performance"></a>Stream Gatherers Performance</h3><p>Stream gatherers are designed to be efficient, but there are some considerations:</p><ul><li>They add a layer of indirection compared to built-in operations</li><li>For simple transformations, traditional operations may still be faster</li><li>Gatherers shine when implementing complex, stateful transformations</li></ul><h3 id="Scoped-Values-vs-ThreadLocals"><a href="#Scoped-Values-vs-ThreadLocals" class="headerlink" title="Scoped Values vs ThreadLocals"></a>Scoped Values vs ThreadLocals</h3><table><thead><tr><th>Aspect</th><th>Scoped Values</th><th>ThreadLocals</th></tr></thead><tbody><tr><td>Performance</td><td>Better for short scopes</td><td>Overhead in high concurrency</td></tr><tr><td>Memory Safety</td><td>Automatically cleaned up</td><td>Prone to leaks</td></tr><tr><td>Immutability</td><td>Enforced</td><td>Not enforced</td></tr><tr><td>Async Support</td><td>Excellent</td><td>Problematic</td></tr></tbody></table><h2 id="Real-World-Use-Cases"><a href="#Real-World-Use-Cases" class="headerlink" title="Real-World Use Cases"></a>Real-World Use Cases</h2><h3 id="Use-Case-1-Distributed-Tracing"><a href="#Use-Case-1-Distributed-Tracing" class="headerlink" title="Use Case 1: Distributed Tracing"></a>Use Case 1: Distributed Tracing</h3><p>Scoped Values are perfect for implementing distributed tracing:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TracingExample</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> ScopedValue&lt;String&gt; TRACE_ID = ScopedValue.newInstance();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">handleRequest</span><span class="params">(Runnable task)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">traceId</span> <span class="operator">=</span> generateTraceId();</span><br><span class="line">        ScopedValue.runWhere(TRACE_ID, traceId, task);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">processStep</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">traceId</span> <span class="operator">=</span> TRACE_ID.get();</span><br><span class="line">        logger.info(<span class="string">&quot;Processing with trace ID: &quot;</span> + traceId);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Use-Case-2-Data-Transformation-Pipeline"><a href="#Use-Case-2-Data-Transformation-Pipeline" class="headerlink" title="Use Case 2: Data Transformation Pipeline"></a>Use Case 2: Data Transformation Pipeline</h3><p>Stream gatherers excel in data transformation pipelines:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DataPipelineExample</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> List&lt;Double&gt; <span class="title function_">processMetrics</span><span class="params">(Stream&lt;Double&gt; metrics)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> metrics.gather(<span class="keyword">new</span> <span class="title class_">RollingAverageGatherer</span>(<span class="number">10</span>))</span><br><span class="line">                     .toList();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">RollingAverageGatherer</span> <span class="keyword">implements</span> <span class="title class_">StreamGatherer</span>&lt;Double, State, Double, Void&gt; &#123;</span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">int</span> windowSize;</span><br><span class="line">        </span><br><span class="line">        RollingAverageGatherer(<span class="type">int</span> windowSize) &#123;</span><br><span class="line">            <span class="built_in">this</span>.windowSize = windowSize;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// Implementation details...</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Key-Takeaways"><a href="#Key-Takeaways" class="headerlink" title="Key Takeaways"></a>Key Takeaways</h2><ol><li><p><strong>Stream Gatherers</strong> provide a powerful way to implement custom stream transformations, especially for stateful operations like windowing and grouping.</p></li><li><p><strong>Scoped Values</strong> offer a modern, efficient alternative to ThreadLocal for passing immutable context through your application, particularly in virtual thread environments.</p></li><li><p><strong>Record Patterns</strong> continue to evolve, making pattern matching more expressive and easier to read.</p></li><li><p><strong>Migration</strong> to Java 24 requires careful review of API changes and thorough testing, especially for code using streams and context passing.</p></li><li><p><strong>Performance</strong> characteristics differ between new features and traditional approaches—choose based on your specific use case rather than defaulting to one approach.</p></li><li><p><strong>Best practices</strong> include using scoped values for immutable context, leveraging gatherers for complex stream transformations, and combining these features with virtual threads for optimal concurrency handling.</p></li></ol><p>Java 24 represents another step forward in making the language more expressive, efficient, and aligned with modern programming patterns. By understanding and adopting these features, you can write cleaner, more maintainable code that takes full advantage of the Java platform’s capabilities.</p>]]>
    </content>
    <id>https://thoughtfly.github.io/devtech/2026/09/14/whats-new-in-java-24-stream-gatherers-scoped-values-and-more/</id>
    <link href="https://thoughtfly.github.io/devtech/2026/09/14/whats-new-in-java-24-stream-gatherers-scoped-values-and-more/"/>
    <published>2026-09-14T16:00:00.000Z</published>
    <summary>Explore Java 24 features including Stream Gatherers for functional transformations, Scoped Values for immutable context passing, and other key updates for mo...</summary>
    <title>What's New in Java 24: Stream Gatherers, Scoped Values, and More</title>
    <updated>2026-09-21T14:46:52.851Z</updated>
  </entry>
  <entry>
    <author>
      <name>DevTech Team</name>
    </author>
    <category term="Java" scheme="https://thoughtfly.github.io/devtech/categories/Java/"/>
    <category term="AI Engineering" scheme="https://thoughtfly.github.io/devtech/categories/Java/AI-Engineering/"/>
    <category term="Java" scheme="https://thoughtfly.github.io/devtech/tags/Java/"/>
    <category term="Spring Boot" scheme="https://thoughtfly.github.io/devtech/tags/Spring-Boot/"/>
    <category term="LLM" scheme="https://thoughtfly.github.io/devtech/tags/LLM/"/>
    <category term="AI" scheme="https://thoughtfly.github.io/devtech/tags/AI/"/>
    <category term="API Design" scheme="https://thoughtfly.github.io/devtech/tags/API-Design/"/>
    <category term="Backend Development" scheme="https://thoughtfly.github.io/devtech/tags/Backend-Development/"/>
    <content>
      <![CDATA[<h2 id="Building-a-Coding-Assistant-Backend-with-Java-and-LLMs"><a href="#Building-a-Coding-Assistant-Backend-with-Java-and-LLMs" class="headerlink" title="Building a Coding Assistant Backend with Java and LLMs"></a>Building a Coding Assistant Backend with Java and LLMs</h2><p>The landscape of developer tools is shifting dramatically. Coding assistants like GitHub Copilot, Amazon CodeWhisperer, and Cursor have fundamentally changed how we write, review, and debug code. But what happens when you need to build your own? Whether it’s for internal tooling, a niche language focus, or keeping sensitive code within your infrastructure, building a coding assistant backend with Java and Large Language Models (LLMs) is a powerful endeavor.</p><p>In this post, we’ll dive deep into the architecture, implementation, and best practices for creating a robust coding assistant backend using Java. We’ll cover everything from selecting the right LLM providers to handling streaming responses, managing context windows, and ensuring security.</p><h3 id="Why-Java-for-LLM-Powered-Applications"><a href="#Why-Java-for-LLM-Powered-Applications" class="headerlink" title="Why Java for LLM-Powered Applications?"></a>Why Java for LLM-Powered Applications?</h3><p>You might wonder why choose Java when Python dominates the AI&#x2F;ML space. The answer lies in enterprise requirements. Java offers:</p><ul><li><strong>Type Safety</strong>: Critical for maintaining large codebases where LLM outputs interact with your application logic</li><li><strong>Performance</strong>: Modern Java (17+) with GraalVM and virtual threads provides excellent throughput</li><li><strong>Ecosystem</strong>: Rich libraries for HTTP clients, streaming, and enterprise integration</li><li><strong>Scalability</strong>: Battle-tested at scale in production environments</li><li><strong>Tooling</strong>: Superior IDE support, debugging, and monitoring capabilities</li></ul><h3 id="Architecture-Overview"><a href="#Architecture-Overview" class="headerlink" title="Architecture Overview"></a>Architecture Overview</h3><p>A coding assistant backend needs to handle several key responsibilities:</p><ol><li><strong>Request Processing</strong>: Accept code snippets, questions, and context from users</li><li><strong>LLM Integration</strong>: Communicate with LLM providers (OpenAI, Anthropic, etc.)</li><li><strong>Context Management</strong>: Maintain conversation history and relevant code context</li><li><strong>Response Streaming</strong>: Deliver real-time responses to users</li><li><strong>Security &amp; Validation</strong>: Sanitize inputs and outputs, manage API keys</li></ol><p>Here’s a high-level architecture:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">┌─────────────┐     ┌──────────────┐     ┌─────────────┐</span><br><span class="line">│   Client    │────▶│  Java Backend│────▶│   LLM API   │</span><br><span class="line">│  (IDE/Web)  │◀────│  (Spring     │◀────│ (OpenAI/    │</span><br><span class="line">│             │     │   Boot)      │     │  Anthropic) │</span><br><span class="line">└─────────────┘     └──────────────┘     └─────────────┘</span><br><span class="line">                            │</span><br><span class="line">                            ▼</span><br><span class="line">                     ┌──────────────┐</span><br><span class="line">                     │  Context     │</span><br><span class="line">                     │  Store       │</span><br><span class="line">                     │  (Redis/     │</span><br><span class="line">                     │  In-Memory)  │</span><br><span class="line">                     └──────────────┘</span><br></pre></td></tr></table></figure><h3 id="Setting-Up-the-Project"><a href="#Setting-Up-the-Project" class="headerlink" title="Setting Up the Project"></a>Setting Up the Project</h3><p>Let’s start with a Spring Boot project. We’ll use Maven for dependency management and include the necessary libraries for HTTP communication, streaming, and LLM integration.</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependencies</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- Spring Boot Web --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-web<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">&lt;!-- Spring WebFlux for reactive streaming --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-webflux<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">&lt;!-- OpenAI Java SDK --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.theokanning.openai-gpt3-java<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>client<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">version</span>&gt;</span>0.18.1<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">&lt;!-- Jackson for JSON processing --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.fasterxml.jackson.core<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>jackson-databind<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">&lt;!-- Lombok for boilerplate reduction --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.projectlombok<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>lombok<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">optional</span>&gt;</span>true<span class="tag">&lt;/<span class="name">optional</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependencies</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="Core-Service-LLM-Integration"><a href="#Core-Service-LLM-Integration" class="headerlink" title="Core Service: LLM Integration"></a>Core Service: LLM Integration</h3><p>The heart of our coding assistant is the LLM integration service. We need to handle different providers and support streaming responses for a better user experience.</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="meta">@RequiredArgsConstructor</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LlmAssistantService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> OpenAiApi openAiApi;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> ContextManager contextManager;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> LlmConfig llmConfig;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Process a coding request and return a streaming response</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> Flux&lt;ChatResponseChunk&gt; <span class="title function_">streamCodingResponse</span><span class="params">(</span></span><br><span class="line"><span class="params">            String userId,</span></span><br><span class="line"><span class="params">            String codeContext,</span></span><br><span class="line"><span class="params">            String userQuery,</span></span><br><span class="line"><span class="params">            Language language)</span> &#123;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// Build conversation history with context</span></span><br><span class="line">        List&lt;ChatCompletionMessage&gt; messages = </span><br><span class="line">            contextManager.getMessages(userId);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// Add system prompt for coding assistant</span></span><br><span class="line">        messages.add(<span class="number">0</span>, ChatCompletionMessage.builder()</span><br><span class="line">            .role(<span class="string">&quot;system&quot;</span>)</span><br><span class="line">            .content(buildSystemPrompt(language))</span><br><span class="line">            .build());</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// Add current code context if provided</span></span><br><span class="line">        <span class="keyword">if</span> (StringUtils.hasText(codeContext)) &#123;</span><br><span class="line">            messages.add(ChatCompletionMessage.builder()</span><br><span class="line">                .role(<span class="string">&quot;user&quot;</span>)</span><br><span class="line">                .content(<span class="string">&quot;Here is the relevant code context:\n&quot;</span> + codeContext)</span><br><span class="line">                .build());</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// Add user query</span></span><br><span class="line">        messages.add(ChatCompletionMessage.builder()</span><br><span class="line">            .role(<span class="string">&quot;user&quot;</span>)</span><br><span class="line">            .content(userQuery)</span><br><span class="line">            .build());</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// Create chat completion request</span></span><br><span class="line">        <span class="type">ChatCompletionRequest</span> <span class="variable">request</span> <span class="operator">=</span> ChatCompletionRequest.builder()</span><br><span class="line">            .model(llmConfig.getModel())</span><br><span class="line">            .messages(messages)</span><br><span class="line">            .temperature(llmConfig.getTemperature())</span><br><span class="line">            .maxTokens(llmConfig.getMaxTokens())</span><br><span class="line">            .stream(<span class="literal">true</span>)</span><br><span class="line">            .build();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// Stream response</span></span><br><span class="line">        <span class="keyword">return</span> Flux.create(sink -&gt; &#123;</span><br><span class="line">            openAiApi.createChatCompletion(request, </span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">ChatCompletionCallback</span>() &#123;</span><br><span class="line">                    <span class="meta">@Override</span></span><br><span class="line">                    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onData</span><span class="params">(String data)</span> &#123;</span><br><span class="line">                        <span class="keyword">try</span> &#123;</span><br><span class="line">                            <span class="type">ChatResponseChunk</span> <span class="variable">chunk</span> <span class="operator">=</span> </span><br><span class="line">                                parseChunk(data);</span><br><span class="line">                            sink.next(chunk);</span><br><span class="line">                        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">                            sink.error(e);</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;</span><br><span class="line">                    </span><br><span class="line">                    <span class="meta">@Override</span></span><br><span class="line">                    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onError</span><span class="params">(Exception e)</span> &#123;</span><br><span class="line">                        sink.error(e);</span><br><span class="line">                    &#125;</span><br><span class="line">                    </span><br><span class="line">                    <span class="meta">@Override</span></span><br><span class="line">                    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onCompleted</span><span class="params">()</span> &#123;</span><br><span class="line">                        <span class="comment">// Update context with new messages</span></span><br><span class="line">                        contextManager.addMessages(userId, messages);</span><br><span class="line">                        sink.complete();</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;);</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> String <span class="title function_">buildSystemPrompt</span><span class="params">(Language language)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> String.format(<span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">            You are an expert Java developer assistant.</span></span><br><span class="line"><span class="string">            Language: %s</span></span><br><span class="line"><span class="string">            </span></span><br><span class="line"><span class="string">            Guidelines:</span></span><br><span class="line"><span class="string">            1. Provide clear, concise code explanations</span></span><br><span class="line"><span class="string">            2. Follow best practices and design patterns</span></span><br><span class="line"><span class="string">            3. Include comments for complex logic</span></span><br><span class="line"><span class="string">            4. Suggest improvements when applicable</span></span><br><span class="line"><span class="string">            5. Maintain security and performance considerations</span></span><br><span class="line"><span class="string">            &quot;&quot;&quot;</span>, language);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> ChatResponseChunk <span class="title function_">parseChunk</span><span class="params">(String data)</span> &#123;</span><br><span class="line">        <span class="comment">// Parse SSE data and extract chunk</span></span><br><span class="line">        <span class="comment">// Implementation depends on LLM provider</span></span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ChatResponseChunk</span>(data);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Context-Management"><a href="#Context-Management" class="headerlink" title="Context Management"></a>Context Management</h3><p>One of the most challenging aspects of building a coding assistant is managing context. LLMs have token limits, and we need to maintain conversation history while staying within those bounds.</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="meta">@RequiredArgsConstructor</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ContextManager</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> RedisTemplate&lt;String, String&gt; redisTemplate;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">MAX_TOKENS</span> <span class="operator">=</span> <span class="number">4000</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">CONTEXT_KEY_PREFIX</span> <span class="operator">=</span> <span class="string">&quot;context:&quot;</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Get conversation history for a user</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> List&lt;ChatCompletionMessage&gt; <span class="title function_">getMessages</span><span class="params">(String userId)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> CONTEXT_KEY_PREFIX + userId;</span><br><span class="line">        <span class="type">String</span> <span class="variable">historyJson</span> <span class="operator">=</span> redisTemplate.opsForValue().get(key);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (historyJson == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> objectMapper.readValue(historyJson, </span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">TypeReference</span>&lt;List&lt;ChatCompletionMessage&gt;&gt;() &#123;&#125;);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Add messages to conversation history</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">addMessages</span><span class="params">(String userId, </span></span><br><span class="line"><span class="params">                           List&lt;ChatCompletionMessage&gt; newMessages)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> CONTEXT_KEY_PREFIX + userId;</span><br><span class="line">        List&lt;ChatCompletionMessage&gt; history = getMessages(userId);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// Add new messages</span></span><br><span class="line">        history.addAll(newMessages);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// Trim to fit within token limit</span></span><br><span class="line">        history = trimToTokenLimit(history, MAX_TOKENS);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// Save back to Redis</span></span><br><span class="line">        redisTemplate.opsForValue().set(key, </span><br><span class="line">            objectMapper.writeValueAsString(history),</span><br><span class="line">            Duration.ofHours(<span class="number">2</span>));</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Estimate tokens and trim history</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> List&lt;ChatCompletionMessage&gt; <span class="title function_">trimToTokenLimit</span><span class="params">(</span></span><br><span class="line"><span class="params">            List&lt;ChatCompletionMessage&gt; messages, </span></span><br><span class="line"><span class="params">            <span class="type">int</span> maxTokens)</span> &#123;</span><br><span class="line">        </span><br><span class="line">        List&lt;ChatCompletionMessage&gt; trimmed = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">        <span class="type">int</span> <span class="variable">totalTokens</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// Start from the end to keep recent context</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> messages.size() - <span class="number">1</span>; i &gt;= <span class="number">0</span>; i--) &#123;</span><br><span class="line">            <span class="type">ChatCompletionMessage</span> <span class="variable">msg</span> <span class="operator">=</span> messages.get(i);</span><br><span class="line">            <span class="type">int</span> <span class="variable">msgTokens</span> <span class="operator">=</span> estimateTokens(msg.getContent());</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">if</span> (totalTokens + msgTokens &lt;= maxTokens) &#123;</span><br><span class="line">                trimmed.add(<span class="number">0</span>, msg); <span class="comment">// Add to beginning to maintain order</span></span><br><span class="line">                totalTokens += msgTokens;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> trimmed;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="title function_">estimateTokens</span><span class="params">(String text)</span> &#123;</span><br><span class="line">        <span class="comment">// Rough estimate: 1 token ≈ 4 characters</span></span><br><span class="line">        <span class="keyword">return</span> (<span class="type">int</span>) Math.ceil((<span class="type">double</span>) text.length() / <span class="number">4</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="REST-Controller"><a href="#REST-Controller" class="headerlink" title="REST Controller"></a>REST Controller</h3><p>Now let’s expose our service through a REST controller with streaming support.</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/api/assistant&quot;)</span></span><br><span class="line"><span class="meta">@RequiredArgsConstructor</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AssistantController</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> LlmAssistantService assistantService;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> ContextManager contextManager;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Stream coding assistance response</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@PostMapping(value = &quot;/chat/stream&quot;, </span></span><br><span class="line"><span class="meta">                produces = MediaType.TEXT_EVENT_STREAM_VALUE)</span></span><br><span class="line">    <span class="keyword">public</span> Flux&lt;ServerSentEvent&lt;String&gt;&gt; <span class="title function_">streamChat</span><span class="params">(</span></span><br><span class="line"><span class="params">            <span class="meta">@RequestHeader(&quot;X-User-Id&quot;)</span> String userId,</span></span><br><span class="line"><span class="params">            <span class="meta">@RequestBody</span> ChatRequest request)</span> &#123;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> assistantService.streamCodingResponse(</span><br><span class="line">                userId,</span><br><span class="line">                request.getCodeContext(),</span><br><span class="line">                request.getQuery(),</span><br><span class="line">                request.getLanguage())</span><br><span class="line">            .map(chunk -&gt; ServerSentEvent.builder(chunk.getContent())</span><br><span class="line">                .event(<span class="string">&quot;message&quot;</span>)</span><br><span class="line">                .build())</span><br><span class="line">            .doOnComplete(() -&gt; </span><br><span class="line">                log.info(<span class="string">&quot;Stream completed for user: &#123;&#125;&quot;</span>, userId))</span><br><span class="line">            .doOnError(error -&gt; </span><br><span class="line">                log.error(<span class="string">&quot;Stream error for user: &#123;&#125;&quot;</span>, userId, error));</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Get conversation history</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/history&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> ResponseEntity&lt;List&lt;ChatCompletionMessage&gt;&gt; <span class="title function_">getHistory</span><span class="params">(</span></span><br><span class="line"><span class="params">            <span class="meta">@RequestHeader(&quot;X-User-Id&quot;)</span> String userId)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> ResponseEntity.ok(</span><br><span class="line">            contextManager.getMessages(userId));</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Clear conversation history</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@DeleteMapping(&quot;/history&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> ResponseEntity&lt;Void&gt; <span class="title function_">clearHistory</span><span class="params">(</span></span><br><span class="line"><span class="params">            <span class="meta">@RequestHeader(&quot;X-User-Id&quot;)</span> String userId)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;context:&quot;</span> + userId;</span><br><span class="line">        redisTemplate.delete(key);</span><br><span class="line">        <span class="keyword">return</span> ResponseEntity.noContent().build();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Request-and-Response-Models"><a href="#Request-and-Response-Models" class="headerlink" title="Request and Response Models"></a>Request and Response Models</h3><p>Let’s define the data models for our API.</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="meta">@Builder</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ChatRequest</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> String query;</span><br><span class="line">    <span class="keyword">private</span> String codeContext;</span><br><span class="line">    <span class="keyword">private</span> Language language;</span><br><span class="line">    <span class="keyword">private</span> Integer maxTokens;</span><br><span class="line">    <span class="keyword">private</span> Double temperature;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="meta">@Builder</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ChatResponseChunk</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> String content;</span><br><span class="line">    <span class="keyword">private</span> Boolean isComplete;</span><br><span class="line">    <span class="keyword">private</span> Long finishReason;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="meta">@Builder</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ChatCompletionMessage</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> String role;</span><br><span class="line">    <span class="keyword">private</span> String content;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">enum</span> <span class="title class_">Language</span> &#123;</span><br><span class="line">    JAVA, PYTHON, TYPESCRIPT, KOTLIN, GO, RUST</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Security-Considerations"><a href="#Security-Considerations" class="headerlink" title="Security Considerations"></a>Security Considerations</h3><p>When building a coding assistant, security is paramount. Here are key considerations:</p><ol><li><strong>API Key Management</strong>: Never hardcode API keys. Use environment variables or a secrets manager.</li></ol><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># application.yml</span></span><br><span class="line"><span class="attr">llm:</span></span><br><span class="line">  <span class="attr">api-key:</span> <span class="string">$&#123;LLM_API_KEY&#125;</span></span><br><span class="line">  <span class="attr">base-url:</span> <span class="string">$&#123;LLM_BASE_URL&#125;</span></span><br><span class="line">  <span class="attr">model:</span> <span class="string">gpt-4</span></span><br><span class="line">  <span class="attr">temperature:</span> <span class="number">0.3</span></span><br><span class="line">  <span class="attr">max-tokens:</span> <span class="number">2000</span></span><br></pre></td></tr></table></figure><ol start="2"><li><strong>Input Sanitization</strong>: Validate and sanitize user inputs to prevent injection attacks.</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">InputValidator</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">validateChatRequest</span><span class="params">(ChatRequest request)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (request == <span class="literal">null</span> || StringUtils.isBlank(request.getQuery())) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;Query is required&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (request.getQuery().length() &gt; <span class="number">10000</span>) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;Query too long&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// Check for suspicious patterns</span></span><br><span class="line">        <span class="keyword">if</span> (containsMaliciousPattern(request.getQuery())) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">SecurityException</span>(<span class="string">&quot;Invalid input detected&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="type">boolean</span> <span class="title function_">containsMaliciousPattern</span><span class="params">(String input)</span> &#123;</span><br><span class="line">        <span class="comment">// Implement pattern matching for injection attacks</span></span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>; <span class="comment">// Simplified for example</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="3"><li><strong>Rate Limiting</strong>: Protect your backend and LLM API from abuse.</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RateLimitConfig</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> RateLimiter <span class="title function_">rateLimiter</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> RateLimiter.of(<span class="string">&quot;assistantApi&quot;</span>, </span><br><span class="line">            RateLimiterConfig.custom()</span><br><span class="line">                .limitForPeriod(<span class="number">100</span>)</span><br><span class="line">                .limitRefreshPeriod(Duration.ofMinutes(<span class="number">1</span>))</span><br><span class="line">                .timeoutDuration(Duration.ofSeconds(<span class="number">5</span>))</span><br><span class="line">                .build());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Performance-Optimization"><a href="#Performance-Optimization" class="headerlink" title="Performance Optimization"></a>Performance Optimization</h3><ol><li><strong>Connection Pooling</strong>: Configure HTTP client connection pooling for better performance.</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">WebClientConfig</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> WebClient <span class="title function_">webClient</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">HttpClient</span> <span class="variable">httpClient</span> <span class="operator">=</span> HttpClient.create()</span><br><span class="line">            .option(ChannelOption.SO_KEEPALIVE, <span class="literal">true</span>)</span><br><span class="line">            .poolFactory(() -&gt; <span class="keyword">new</span> <span class="title class_">PoolingHttpClientConnectionManager</span>());</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> WebClient.builder()</span><br><span class="line">            .clientConnector(<span class="keyword">new</span> <span class="title class_">ReactorClientHttpConnector</span>(httpClient))</span><br><span class="line">            .build();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="2"><li><strong>Caching</strong>: Cache frequent responses to reduce LLM API calls.</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="meta">@RequiredArgsConstructor</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CachingLlmService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> LlmAssistantService llmService;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> CacheManager cacheManager;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Cacheable(value = &quot;assistantResponses&quot;, key = &quot;#userId + &#x27;#&#x27; + #query&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getCachedResponse</span><span class="params">(String userId, String query)</span> &#123;</span><br><span class="line">        <span class="comment">// This would need to be adapted for streaming</span></span><br><span class="line">        <span class="keyword">return</span> llmService.getNonStreamingResponse(userId, query);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Testing-Strategy"><a href="#Testing-Strategy" class="headerlink" title="Testing Strategy"></a>Testing Strategy</h3><p>Testing an LLM-powered service requires a different approach. We need to test:</p><ol><li><strong>Unit Tests</strong>: Test individual components in isolation</li><li><strong>Integration Tests</strong>: Test with mock LLM responses</li><li><strong>Contract Tests</strong>: Ensure API contracts are maintained</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootTest</span></span><br><span class="line"><span class="meta">@ActiveProfiles(&quot;test&quot;)</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">AssistantControllerTest</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> WebTestClient webTestClient;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@MockBean</span></span><br><span class="line">    <span class="keyword">private</span> LlmAssistantService assistantService;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">shouldStreamCodingResponse</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// Arrange</span></span><br><span class="line">        <span class="type">ChatResponseChunk</span> <span class="variable">chunk</span> <span class="operator">=</span> ChatResponseChunk.builder()</span><br><span class="line">            .content(<span class="string">&quot;Here&#x27;s the solution:&quot;</span>)</span><br><span class="line">            .build();</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">when</span>(assistantService.streamCodingResponse(</span><br><span class="line">            any(), any(), any(), any()))</span><br><span class="line">            .thenReturn(Flux.just(chunk));</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// Act &amp; Assert</span></span><br><span class="line">        webTestClient.post()</span><br><span class="line">            .uri(<span class="string">&quot;/api/assistant/chat/stream&quot;</span>)</span><br><span class="line">            .header(<span class="string">&quot;X-User-Id&quot;</span>, <span class="string">&quot;test-user&quot;</span>)</span><br><span class="line">            .contentType(MediaType.APPLICATION_JSON)</span><br><span class="line">            .bodyValue(<span class="keyword">new</span> <span class="title class_">ChatRequest</span>(<span class="string">&quot;Fix this bug&quot;</span>, <span class="literal">null</span>, JAVA))</span><br><span class="line">            .accept(MediaType.TEXT_EVENT_STREAM)</span><br><span class="line">            .exchange()</span><br><span class="line">            .expectStatus().isOk()</span><br><span class="line">            .expectBody()</span><br><span class="line">            .jsonPath(<span class="string">&quot;$.data.content&quot;</span>).isEqualTo(<span class="string">&quot;Here&#x27;s the solution:&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Deployment-Considerations"><a href="#Deployment-Considerations" class="headerlink" title="Deployment Considerations"></a>Deployment Considerations</h3><ol><li><strong>Containerization</strong>: Package your application with Docker for consistent deployments.</li></ol><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">FROM</span> eclipse-temurin:<span class="number">17</span>-jre-alpine</span><br><span class="line"><span class="keyword">WORKDIR</span><span class="language-bash"> /app</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> target/*.jar app.jar</span></span><br><span class="line"><span class="keyword">EXPOSE</span> <span class="number">8080</span></span><br><span class="line"><span class="keyword">ENTRYPOINT</span><span class="language-bash"> [<span class="string">&quot;java&quot;</span>, <span class="string">&quot;-jar&quot;</span>, <span class="string">&quot;app.jar&quot;</span>]</span></span><br></pre></td></tr></table></figure><ol start="2"><li><strong>Health Checks</strong>: Implement health checks for Kubernetes or container orchestration.</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HealthController</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@GetMapping(&quot;/health&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> ResponseEntity&lt;Map&lt;String, String&gt;&gt; <span class="title function_">health</span><span class="params">()</span> &#123;</span><br><span class="line">        Map&lt;String, String&gt; status = Map.of(</span><br><span class="line">            <span class="string">&quot;status&quot;</span>, <span class="string">&quot;UP&quot;</span>,</span><br><span class="line">            <span class="string">&quot;service&quot;</span>, <span class="string">&quot;coding-assistant&quot;</span></span><br><span class="line">        );</span><br><span class="line">        <span class="keyword">return</span> ResponseEntity.ok(status);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="3"><li><strong>Monitoring</strong>: Integrate with monitoring tools like Prometheus and Grafana.</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MonitoringConfig</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> MeterRegistryCustomizer&lt;MeterRegistry&gt; <span class="title function_">metricsCommonTags</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> registry -&gt; registry.config()</span><br><span class="line">            .commonTags(<span class="string">&quot;application&quot;</span>, <span class="string">&quot;coding-assistant&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Key-Takeaways"><a href="#Key-Takeaways" class="headerlink" title="Key Takeaways"></a>Key Takeaways</h3><ul><li><strong>Architecture Matters</strong>: Design your backend with clear separation of concerns between LLM integration, context management, and API layer</li><li><strong>Streaming is Essential</strong>: Use reactive programming with WebFlux for real-time responses that improve user experience</li><li><strong>Context Management</strong>: Implement smart token management to maintain conversation history within LLM limits</li><li><strong>Security First</strong>: Always validate inputs, manage API keys securely, and implement rate limiting</li><li><strong>Testing Strategy</strong>: Combine unit tests with mock-based integration tests for reliable coverage</li><li><strong>Performance Optimization</strong>: Use connection pooling, caching, and proper HTTP client configuration</li><li><strong>Production Readiness</strong>: Include health checks, monitoring, and containerization for smooth deployments</li></ul><p>Building a coding assistant backend with Java and LLMs is challenging but rewarding. By following these patterns and best practices, you can create a robust, scalable, and secure service that enhances developer productivity. The key is to start with a solid architecture, iterate based on user feedback, and continuously improve your context management and response quality.</p>]]>
    </content>
    <id>https://thoughtfly.github.io/devtech/2026/09/13/building-a-coding-assistant-backend-with-java-and-llms/</id>
    <link href="https://thoughtfly.github.io/devtech/2026/09/13/building-a-coding-assistant-backend-with-java-and-llms/"/>
    <published>2026-09-13T16:00:00.000Z</published>
    <summary>Learn how to build a production-ready coding assistant backend using Java, Spring Boot, and LLMs. Includes architecture, code examples, and best practices.</summary>
    <title>Building a Coding Assistant Backend with Java and LLMs</title>
    <updated>2026-09-21T14:46:52.851Z</updated>
  </entry>
  <entry>
    <author>
      <name>DevTech Team</name>
    </author>
    <category term="Machine Learning" scheme="https://thoughtfly.github.io/devtech/categories/Machine-Learning/"/>
    <category term="Engineering" scheme="https://thoughtfly.github.io/devtech/categories/Machine-Learning/Engineering/"/>
    <category term="RAG" scheme="https://thoughtfly.github.io/devtech/tags/RAG/"/>
    <category term="LLM" scheme="https://thoughtfly.github.io/devtech/tags/LLM/"/>
    <category term="Evaluation" scheme="https://thoughtfly.github.io/devtech/tags/Evaluation/"/>
    <category term="MLOps" scheme="https://thoughtfly.github.io/devtech/tags/MLOps/"/>
    <category term="LangChain" scheme="https://thoughtfly.github.io/devtech/tags/LangChain/"/>
    <content>
      <![CDATA[<h2 id="Introduction"><a href="#Introduction" class="headerlink" title="Introduction"></a>Introduction</h2><p>Retrieval-Augmented Generation (RAG) has become the standard architecture for building enterprise-grade AI applications. By combining the factual grounding of retrieval with the generative power of Large Language Models (LLMs), RAG systems can answer questions based on proprietary data without the prohibitive cost of fine-tuning. However, building a RAG pipeline is only half the battle; ensuring it performs reliably in production is where most teams struggle.</p><p>Unlike traditional software testing, evaluating LLM outputs is inherently probabilistic and subjective. A response might be factually correct but poorly structured, or grammatically perfect but based on fabricated information. To address this, the field has converged on three core evaluation metrics: <strong>Faithfulness</strong>, <strong>Relevance</strong>, and <strong>Hallucination</strong>. These metrics provide a quantitative framework for assessing the quality of your RAG system, moving beyond vague “gut feelings” to actionable data.</p><p>In this post, we will dive deep into each metric, explain how they interrelate, and provide practical Python implementations using industry-standard tools like LangChain and LLM-as-a-judge patterns. Whether you are building your first RAG app or optimizing a production pipeline, understanding these metrics is essential for delivering trustworthy AI.</p><h2 id="Why-RAG-Evaluation-is-Different"><a href="#Why-RAG-Evaluation-is-Different" class="headerlink" title="Why RAG Evaluation is Different"></a>Why RAG Evaluation is Different</h2><p>Before we define the metrics, it is crucial to understand why evaluating RAG is harder than evaluating a standard classification model. In traditional machine learning, we compare predictions against ground-truth labels using deterministic metrics like accuracy or F1-score. In RAG, we are dealing with open-ended text generation. There is rarely a single “correct” answer; instead, there is a spectrum of quality.</p><p>Furthermore, RAG is a multi-stage pipeline. Errors can occur at any point:</p><ol><li><strong>Retrieval Failure:</strong> The system fails to find the relevant documents.</li><li><strong>Context Misuse:</strong> The system finds the right documents but ignores them.</li><li><strong>Generation Error:</strong> The system hallucinates information not present in the context.</li></ol><p>A robust evaluation strategy must isolate these stages. This is where Faithfulness, Relevance, and Hallucination metrics come into play. They allow us to diagnose exactly where the pipeline is breaking and where it is succeeding.</p><h2 id="Metric-1-Faithfulness"><a href="#Metric-1-Faithfulness" class="headerlink" title="Metric 1: Faithfulness"></a>Metric 1: Faithfulness</h2><h3 id="What-is-Faithfulness"><a href="#What-is-Faithfulness" class="headerlink" title="What is Faithfulness?"></a>What is Faithfulness?</h3><p>Faithfulness measures the extent to which the generated answer is grounded in the retrieved context. It answers the question: <em>“Does the model stick to the facts provided in the context, or does it bring in outside knowledge?”</em></p><p>A high faithfulness score means the LLM did not invent facts, misinterpret the source material, or ignore contradictory evidence within the context. Faithfulness is particularly important in enterprise settings where accuracy is non-negotiable. If a customer support bot cites a policy document, the response must strictly adhere to that document.</p><h3 id="How-to-Measure-Faithfulness"><a href="#How-to-Measure-Faithfulness" class="headerlink" title="How to Measure Faithfulness"></a>How to Measure Faithfulness</h3><p>The most effective way to measure faithfulness is using an <strong>LLM-as-a-Judge</strong> approach. We ask a powerful LLM (the judge) to act as an auditor. The judge receives the query, the retrieved context, and the generated answer. It then evaluates whether every claim in the answer can be directly traced back to the context.</p><p>Here is a conceptual breakdown of the evaluation logic:</p><ol><li>Break the generated answer into individual claims.</li><li>For each claim, check if it is supported by the context.</li><li>Calculate the ratio of supported claims to total claims.</li></ol><h3 id="Practical-Implementation"><a href="#Practical-Implementation" class="headerlink" title="Practical Implementation"></a>Practical Implementation</h3><p>Let’s look at how to implement this using Python and LangChain. We will use a simple prompt template to instruct the LLM judge.</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> langchain.evaluation <span class="keyword">import</span> load_evaluator</span><br><span class="line"><span class="keyword">from</span> langchain.chat_models <span class="keyword">import</span> ChatOpenAI</span><br><span class="line"></span><br><span class="line"><span class="comment"># Initialize the evaluator</span></span><br><span class="line">llm = ChatOpenAI(model=<span class="string">&quot;gpt-4-turbo&quot;</span>, temperature=<span class="number">0</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># Load the faithfulness evaluator</span></span><br><span class="line"><span class="comment"># This uses a predefined prompt that checks if the answer is supported by context</span></span><br><span class="line">evaluator = load_evaluator(</span><br><span class="line">    <span class="string">&quot;labeled_criteria&quot;</span>,</span><br><span class="line">    criteria=<span class="string">&quot;helpfulness&quot;</span>,</span><br><span class="line">    llm=llm</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment"># Example data</span></span><br><span class="line">query = <span class="string">&quot;What is the return policy for electronics?&quot;</span></span><br><span class="line">context = <span class="string">&quot;Electronics can be returned within 30 days. Laptops require a restocking fee.&quot;</span></span><br><span class="line">answer = <span class="string">&quot;You can return electronics within 30 days, and laptops have a restocking fee.&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Evaluate</span></span><br><span class="line">result = evaluator.evaluate_string_pairs(</span><br><span class="line">    prediction=answer,</span><br><span class="line">    <span class="built_in">input</span>=query,</span><br><span class="line">    reference=context</span><br><span class="line">)</span><br><span class="line"><span class="built_in">print</span>(result)</span><br></pre></td></tr></table></figure><p>In production, you might want to build a custom evaluator that outputs a binary score (0 or 1) or a confidence percentage. The key is to ensure the judge prompt explicitly instructs the model to penalize any information not found in the context.</p><h2 id="Metric-2-Relevance"><a href="#Metric-2-Relevance" class="headerlink" title="Metric 2: Relevance"></a>Metric 2: Relevance</h2><h3 id="What-is-Relevance"><a href="#What-is-Relevance" class="headerlink" title="What is Relevance?"></a>What is Relevance?</h3><p>Relevance measures the degree to which the generated answer addresses the user’s query. It answers the question: <em>“Did the model answer the specific question asked?”</em></p><p>Unlike faithfulness, which focuses on the relationship between the answer and the context, relevance focuses on the relationship between the answer and the query. A response can be perfectly faithful to the context but completely irrelevant to the user’s intent. For example, if a user asks, “How do I reset my password?” and the system returns a detailed history of password policies, the answer is faithful to the context but irrelevant to the query.</p><h3 id="Dimensions-of-Relevance"><a href="#Dimensions-of-Relevance" class="headerlink" title="Dimensions of Relevance"></a>Dimensions of Relevance</h3><p>Relevance is often broken down into two sub-dimensions:</p><ol><li><strong>Semantic Relevance:</strong> Does the answer mean the same thing as the query? This is best measured using embedding similarity.</li><li><strong>Contextual Relevance:</strong> Does the answer directly address the user’s intent? This is best measured using LLM evaluation.</li></ol><h3 id="Practical-Implementation-1"><a href="#Practical-Implementation-1" class="headerlink" title="Practical Implementation"></a>Practical Implementation</h3><p>For semantic relevance, we can use cosine similarity between the query embedding and the answer embedding. However, this often fails to capture nuanced intent. Therefore, LLM-based evaluation is preferred for relevance.</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> langchain.embeddings <span class="keyword">import</span> OpenAIEmbeddings</span><br><span class="line"><span class="keyword">from</span> langchain.vectorstores <span class="keyword">import</span> Chroma</span><br><span class="line"></span><br><span class="line"><span class="comment"># Semantic Relevance via Embeddings</span></span><br><span class="line">embeddings = OpenAIEmbeddings()</span><br><span class="line">query_embedding = embeddings.embed_query(<span class="string">&quot;How do I reset my password?&quot;</span>)</span><br><span class="line">answer_embedding = embeddings.embed_query(<span class="string">&quot;To reset your password, go to settings and click forgot password.&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># Calculate cosine similarity</span></span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line">similarity = np.dot(query_embedding, answer_embedding) / (np.linalg.norm(query_embedding) * np.linalg.norm(answer_embedding))</span><br><span class="line"><span class="built_in">print</span>(<span class="string">f&quot;Semantic Similarity: <span class="subst">&#123;similarity:<span class="number">.4</span>f&#125;</span>&quot;</span>)</span><br></pre></td></tr></table></figure><p>For contextual relevance, we again rely on an LLM judge. The prompt should ask the model to rate how well the answer satisfies the query on a scale of 1-5.</p><h2 id="Metric-3-Hallucination"><a href="#Metric-3-Hallucination" class="headerlink" title="Metric 3: Hallucination"></a>Metric 3: Hallucination</h2><h3 id="What-is-Hallucination"><a href="#What-is-Hallucination" class="headerlink" title="What is Hallucination?"></a>What is Hallucination?</h3><p>Hallucination is the presence of factually incorrect or fabricated information in the generated answer. It is the most dangerous failure mode in RAG systems because it erodes user trust. A hallucination can look like a plausible fact that is entirely made up by the model.</p><p>Hallucinations can take several forms:</p><ul><li><strong>Object Hallucination:</strong> Mentioning entities that do not exist in the context.</li><li><strong>Attribute Hallucination:</strong> Assigning incorrect properties to existing entities.</li><li><strong>Logical Hallucination:</strong> Drawing incorrect conclusions from the context.</li></ul><h3 id="Measuring-Hallucination"><a href="#Measuring-Hallucination" class="headerlink" title="Measuring Hallucination"></a>Measuring Hallucination</h3><p>Hallucination is essentially the inverse of faithfulness. If faithfulness measures how much of the answer is supported by the context, hallucination measures how much is not. We can detect hallucinations by comparing the generated answer against the retrieved context and identifying unsupported claims.</p><h3 id="Practical-Implementation-2"><a href="#Practical-Implementation-2" class="headerlink" title="Practical Implementation"></a>Practical Implementation</h3><p>We can use a specialized hallucination evaluator or build a custom one. The key is to instruct the LLM to identify any statement in the answer that is not present in the context.</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">HALLUCINATION_PROMPT = <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">You are an expert at detecting hallucinations in AI-generated text.</span></span><br><span class="line"><span class="string">Your task is to identify any claims in the answer that are not supported by the context.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">Context: &#123;context&#125;</span></span><br><span class="line"><span class="string">Answer: &#123;answer&#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">If the answer contains information not found in the context, output &#x27;HALLUCINATION&#x27;.</span></span><br><span class="line"><span class="string">If the answer is fully supported by the context, output &#x27;NO HALLUCINATION&#x27;.</span></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">check_hallucination</span>(<span class="params">context, answer, llm</span>):</span><br><span class="line">    response = llm.invoke(HALLUCINATION_PROMPT.<span class="built_in">format</span>(context=context, answer=answer))</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;HALLUCINATION&quot;</span> <span class="keyword">in</span> response.content</span><br></pre></td></tr></table></figure><h2 id="The-Evaluation-Pipeline"><a href="#The-Evaluation-Pipeline" class="headerlink" title="The Evaluation Pipeline"></a>The Evaluation Pipeline</h2><p>Evaluating RAG systems is not a one-time exercise; it should be an ongoing process integrated into your CI&#x2F;CD pipeline. Here is a recommended workflow:</p><ol><li><strong>Build a Golden Dataset:</strong> Create a set of (query, context, ground_truth_answer) triples. This dataset should cover a wide range of scenarios, including edge cases and ambiguous queries.</li><li><strong>Run Batch Evaluation:</strong> Use your evaluation scripts to score all queries in the golden dataset.</li><li><strong>Analyze Results:</strong> Look for patterns in failures. Are hallucinations common in long contexts? Is relevance low for complex queries?</li><li><strong>Iterate:</strong> Adjust your retrieval strategy, prompt engineering, or chunking size based on the insights.</li><li><strong>Monitor in Production:</strong> Continuously evaluate real user queries to catch drift and new failure modes.</li></ol><h2 id="Tools-for-RAG-Evaluation"><a href="#Tools-for-RAG-Evaluation" class="headerlink" title="Tools for RAG Evaluation"></a>Tools for RAG Evaluation</h2><p>Several tools can simplify the evaluation process:</p><ul><li><strong>LangChain:</strong> Offers built-in evaluators for faithfulness, relevance, and hallucination.</li><li><strong>Ragas:</strong> A specialized open-source framework for RAG evaluation that provides comprehensive metrics and dashboards.</li><li><strong>DeepEval:</strong> A testing framework for LLM applications that supports custom metrics.</li><li><strong>Arize Phoenix:</strong> A tracing and evaluation platform that helps visualize RAG performance.</li></ul><h2 id="Key-Takeaways"><a href="#Key-Takeaways" class="headerlink" title="Key Takeaways"></a>Key Takeaways</h2><ul><li><strong>Faithfulness</strong> ensures the generated answer is grounded in the retrieved context, preventing the model from bringing in outside knowledge.</li><li><strong>Relevance</strong> measures how well the answer addresses the user’s query, ensuring the response is useful and on-topic.</li><li><strong>Hallucination</strong> detects fabricated or incorrect information, which is critical for maintaining user trust in enterprise applications.</li><li><strong>LLM-as-a-Judge</strong> is the most effective method for evaluating these metrics, leveraging powerful LLMs to assess the quality of generated text.</li><li><strong>Continuous Evaluation</strong> is essential; build a golden dataset and integrate evaluation into your CI&#x2F;CD pipeline to catch regressions early.</li><li><strong>Tooling Matters:</strong> Use frameworks like LangChain, Ragas, or DeepEval to streamline the evaluation process and gain actionable insights.</li></ul><p>By rigorously evaluating your RAG systems using these metrics, you can move from guesswork to data-driven optimization, ensuring your AI applications are accurate, reliable, and trustworthy.</p>]]>
    </content>
    <id>https://thoughtfly.github.io/devtech/2026/09/12/rag-evaluation-metrics-faithfulness-relevance-and-hallucination/</id>
    <link href="https://thoughtfly.github.io/devtech/2026/09/12/rag-evaluation-metrics-faithfulness-relevance-and-hallucination/"/>
    <published>2026-09-12T16:00:00.000Z</published>
    <summary>Learn how to evaluate RAG systems using faithfulness, relevance, and hallucination metrics with practical Python examples and LLM-as-a-judge techniques.</summary>
    <title>RAG Evaluation Metrics: Faithfulness, Relevance, and Hallucination</title>
    <updated>2026-09-21T14:46:52.851Z</updated>
  </entry>
  <entry>
    <author>
      <name>DevTech Team</name>
    </author>
    <category term="Java" scheme="https://thoughtfly.github.io/devtech/categories/Java/"/>
    <category term="Java" scheme="https://thoughtfly.github.io/devtech/tags/Java/"/>
    <category term="LLM" scheme="https://thoughtfly.github.io/devtech/tags/LLM/"/>
    <category term="Spring AI" scheme="https://thoughtfly.github.io/devtech/tags/Spring-AI/"/>
    <category term="LangChain4j" scheme="https://thoughtfly.github.io/devtech/tags/LangChain4j/"/>
    <category term="Function Calling" scheme="https://thoughtfly.github.io/devtech/tags/Function-Calling/"/>
    <category term="Agents" scheme="https://thoughtfly.github.io/devtech/tags/Agents/"/>
    <category term="Tool-Use" scheme="https://thoughtfly.github.io/devtech/tags/Tool-Use/"/>
    <content>
      <![CDATA[<h2 id="The-Java-Developer’s-Guide-to-Building-LLM-Agents"><a href="#The-Java-Developer’s-Guide-to-Building-LLM-Agents" class="headerlink" title="The Java Developer’s Guide to Building LLM Agents"></a>The Java Developer’s Guide to Building LLM Agents</h2><p>If you’ve spent any time in the Java ecosystem recently, you’ve likely noticed a surge of interest in Large Language Models (LLMs) and how to integrate them into enterprise applications. For years, Java has been the backbone of backend systems—robust, type-safe, and battle-tested. But when it comes to AI-driven features, the conversation has often centered around Python. That’s changing fast.</p><p>Today, we’re exploring how Java developers can build <strong>tool-use frameworks</strong> that empower LLMs to call external functions, access APIs, and make decisions—transforming simple chatbots into <strong>autonomous agents</strong>.</p><p>Whether you’re evaluating <strong>Spring AI</strong>, <strong>LangChain4j</strong>, or building custom integrations, this post will walk you through the architecture, patterns, and code needed to go from basic function calling to full-blown agent loops.</p><h2 id="Why-Tool-Use-Matters-in-Java"><a href="#Why-Tool-Use-Matters-in-Java" class="headerlink" title="Why Tool-Use Matters in Java"></a>Why Tool-Use Matters in Java</h2><p>LLMs are incredibly capable at generating text, but they’re <strong>stateless</strong> and <strong>hallucinate</strong>. They don’t have real-time access to your database, your company’s knowledge base, or your internal APIs. Without tool-use, an LLM is just a fancy autocomplete engine.</p><p><strong>Tool-use</strong> bridges this gap. It allows the model to:</p><ul><li>Call a function to fetch real-time data (e.g., current stock price)</li><li>Execute a database query</li><li>Trigger a workflow in your system</li><li>Retrieve information from a vector store</li></ul><p>In Java, this is particularly powerful because you can leverage the entire ecosystem—Spring Boot, Hibernate, Kafka, Kubernetes operators—while benefiting from the reasoning capabilities of modern LLMs.</p><h2 id="Core-Concepts-Function-Calling-vs-Agents"><a href="#Core-Concepts-Function-Calling-vs-Agents" class="headerlink" title="Core Concepts: Function Calling vs. Agents"></a>Core Concepts: Function Calling vs. Agents</h2><p>Before diving into frameworks, let’s clarify two often-confused terms:</p><h3 id="Function-Calling"><a href="#Function-Calling" class="headerlink" title="Function Calling"></a>Function Calling</h3><p><strong>Function calling</strong> (also known as tool calling) is the mechanism by which an LLM decides to invoke a specific function with structured arguments. The model doesn’t execute the function itself; it returns a structured request (usually JSON) that your code then executes.</p><p>Example:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">User: &quot;What’s the weather in Tokyo?&quot;</span><br><span class="line">Model: &#123;&quot;function&quot;: &quot;get_weather&quot;, &quot;arguments&quot;: &#123;&quot;city&quot;: &quot;Tokyo&quot;&#125;&#125;</span><br></pre></td></tr></table></figure><p>Your Java code then calls <code>getWeather(&quot;Tokyo&quot;)</code> and feeds the result back to the model.</p><h3 id="Agents"><a href="#Agents" class="headerlink" title="Agents"></a>Agents</h3><p>An <strong>agent</strong> is a system that uses function calling in a <strong>loop</strong>. The agent:</p><ol><li>Receives a user prompt</li><li>Decides which tool(s) to call</li><li>Executes the tool</li><li>Observes the result</li><li>Decides what to do next (call another tool, answer, or ask for clarification)</li></ol><p>Agents are more autonomous and can handle complex, multi-step tasks.</p><h2 id="The-Java-Ecosystem-Key-Frameworks"><a href="#The-Java-Ecosystem-Key-Frameworks" class="headerlink" title="The Java Ecosystem: Key Frameworks"></a>The Java Ecosystem: Key Frameworks</h2><p>Two frameworks dominate the Java landscape for building tool-use systems:</p><ol><li><strong>Spring AI</strong> – Backed by VMware&#x2F;Pivotal, integrates naturally with Spring Boot.</li><li><strong>LangChain4j</strong> – A pure Java port of Python’s LangChain, focused on flexibility and composability.</li></ol><p>Both support function calling and agent patterns. Let’s explore how to use them.</p><h2 id="Setting-Up-Spring-AI-for-Tool-Calling"><a href="#Setting-Up-Spring-AI-for-Tool-Calling" class="headerlink" title="Setting Up Spring AI for Tool Calling"></a>Setting Up Spring AI for Tool Calling</h2><p>Spring AI provides a <code>FunctionCallback</code> abstraction that makes it easy to expose Java methods to LLMs.</p><h3 id="Step-1-Add-Dependencies"><a href="#Step-1-Add-Dependencies" class="headerlink" title="Step 1: Add Dependencies"></a>Step 1: Add Dependencies</h3><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.ai<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-ai-openai-spring-boot-starter<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-web<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="Step-2-Define-Your-Tool"><a href="#Step-2-Define-Your-Tool" class="headerlink" title="Step 2: Define Your Tool"></a>Step 2: Define Your Tool</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">WeatherTool</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Function</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getWeather</span><span class="params">(<span class="meta">@Description(&quot;City name&quot;)</span> String city)</span> &#123;</span><br><span class="line">        <span class="comment">// In production, call a real weather API</span></span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;Sunny, 22°C in &quot;</span> + city;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Step-3-Configure-the-Chat-Client"><a href="#Step-3-Configure-the-Chat-Client" class="headerlink" title="Step 3: Configure the Chat Client"></a>Step 3: Configure the Chat Client</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AiConfig</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> ChatClient <span class="title function_">chatClient</span><span class="params">(ChatClient.Builder builder)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> builder.build();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> FunctionCallback <span class="title function_">weatherFunctionCallback</span><span class="params">(WeatherTool weatherTool)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> MethodToolCallback.builder()</span><br><span class="line">                .toolObject(weatherTool)</span><br><span class="line">                .methodName(<span class="string">&quot;getWeather&quot;</span>)</span><br><span class="line">                .build();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Step-4-Use-the-Tool-in-a-Prompt"><a href="#Step-4-Use-the-Tool-in-a-Prompt" class="headerlink" title="Step 4: Use the Tool in a Prompt"></a>Step 4: Use the Tool in a Prompt</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">WeatherService</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> ChatClient chatClient;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> FunctionCallback weatherCallback;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">WeatherService</span><span class="params">(ChatClient chatClient,</span></span><br><span class="line"><span class="params">                          FunctionCallback weatherCallback)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.chatClient = chatClient;</span><br><span class="line">        <span class="built_in">this</span>.weatherCallback = weatherCallback;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">askWeather</span><span class="params">(String question)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> chatClient.prompt()</span><br><span class="line">                .functions(weatherCallback)</span><br><span class="line">                .user(question)</span><br><span class="line">                .call()</span><br><span class="line">                .content();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>When you call <code>askWeather(&quot;What’s the weather in Tokyo?&quot;)</code>, Spring AI will:</p><ol><li>Send the prompt to OpenAI with the function definition</li><li>Receive a function call request</li><li>Execute <code>getWeather(&quot;Tokyo&quot;)</code></li><li>Send the result back to the model</li><li>Return a natural language answer</li></ol><h2 id="Building-Agents-with-LangChain4j"><a href="#Building-Agents-with-LangChain4j" class="headerlink" title="Building Agents with LangChain4j"></a>Building Agents with LangChain4j</h2><p>LangChain4j takes a more modular approach. Agents are built using <strong>Tool</strong> annotations and a <strong>ToolExecutor</strong>.</p><h3 id="Step-1-Add-Dependencies-1"><a href="#Step-1-Add-Dependencies-1" class="headerlink" title="Step 1: Add Dependencies"></a>Step 1: Add Dependencies</h3><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>dev.langchain4j<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>langchain4j-spring-boot-starter<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>dev.langchain4j<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>langchain4j-open-ai-spring-boot-starter<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="Step-2-Define-Tools"><a href="#Step-2-Define-Tools" class="headerlink" title="Step 2: Define Tools"></a>Step 2: Define Tools</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Tool(description = &quot;Get current weather for a city&quot;)</span></span><br><span class="line"><span class="keyword">public</span> String <span class="title function_">getWeather</span><span class="params">(<span class="meta">@P(&quot;City name&quot;)</span> String city)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;Sunny, 22°C in &quot;</span> + city;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Tool(description = &quot;Calculate the total price of items&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">double</span> <span class="title function_">calculateTotal</span><span class="params">(<span class="meta">@P(&quot;List of item prices&quot;)</span> List&lt;Double&gt; prices)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> prices.stream().mapToDouble(Double::doubleValue).sum();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Step-3-Create-an-Agent"><a href="#Step-3-Create-an-Agent" class="headerlink" title="Step 3: Create an Agent"></a>Step 3: Create an Agent</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AgentService</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> ChatLanguageModel chatModel;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> ToolProvider toolProvider;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">AgentService</span><span class="params">(ChatLanguageModel chatModel,</span></span><br><span class="line"><span class="params">                        ToolProvider toolProvider)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.chatModel = chatModel;</span><br><span class="line">        <span class="built_in">this</span>.toolProvider = toolProvider;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">chat</span><span class="params">(String userMessage)</span> &#123;</span><br><span class="line">        <span class="comment">// Build the agent</span></span><br><span class="line">        <span class="type">Agent</span> <span class="variable">agent</span> <span class="operator">=</span> Agent.builder()</span><br><span class="line">                .chatLanguageModel(chatModel)</span><br><span class="line">                .tools(toolProvider.getTools())</span><br><span class="line">                .build();</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Execute the agent</span></span><br><span class="line">        <span class="keyword">return</span> agent.chat(userMessage);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>LangChain4j handles the agent loop internally. The agent will call tools as needed until it can answer the user’s question.</p><h2 id="Advanced-Pattern-Multi-Step-Agents"><a href="#Advanced-Pattern-Multi-Step-Agents" class="headerlink" title="Advanced Pattern: Multi-Step Agents"></a>Advanced Pattern: Multi-Step Agents</h2><p>Real-world agents often need to chain multiple tool calls. For example:</p><ol><li>Search for a product</li><li>Get its price</li><li>Check inventory</li><li>Place an order</li></ol><h3 id="Spring-AI-Manual-Agent-Loop"><a href="#Spring-AI-Manual-Agent-Loop" class="headerlink" title="Spring AI: Manual Agent Loop"></a>Spring AI: Manual Agent Loop</h3><p>Spring AI doesn’t include a built-in agent loop, so you implement it:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> String <span class="title function_">multiStepAgent</span><span class="params">(String prompt)</span> &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">conversationHistory</span> <span class="operator">=</span> prompt;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">5</span>; i++) &#123; <span class="comment">// Max iterations</span></span><br><span class="line">        <span class="type">ChatResponse</span> <span class="variable">response</span> <span class="operator">=</span> chatClient.prompt()</span><br><span class="line">                .functions(weatherCallback, orderCallback)</span><br><span class="line">                .system(conversationHistory)</span><br><span class="line">                .call()</span><br><span class="line">                .chatResponse();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (response.hasToolCalls()) &#123;</span><br><span class="line">            <span class="type">ToolResponse</span> <span class="variable">toolResponse</span> <span class="operator">=</span> executeToolCalls(response.toolCalls());</span><br><span class="line">            conversationHistory += <span class="string">&quot;\nTool result: &quot;</span> + toolResponse;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> response.getResult().getOutput().getContent();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;Max iterations reached&quot;</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="LangChain4j-Built-In-Agent"><a href="#LangChain4j-Built-In-Agent" class="headerlink" title="LangChain4j: Built-In Agent"></a>LangChain4j: Built-In Agent</h3><p>LangChain4j’s agent abstraction handles this automatically:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Agent</span> <span class="variable">agent</span> <span class="operator">=</span> Agent.builder()</span><br><span class="line">        .chatLanguageModel(chatModel)</span><br><span class="line">        .tools(searchTool, orderTool, inventoryTool)</span><br><span class="line">        .maxIterations(<span class="number">10</span>)</span><br><span class="line">        .build();</span><br><span class="line"></span><br><span class="line"><span class="type">String</span> <span class="variable">result</span> <span class="operator">=</span> agent.chat(<span class="string">&quot;Find the cheapest laptop and add it to cart&quot;</span>);</span><br></pre></td></tr></table></figure><h2 id="Best-Practices-for-Java-Tool-Use"><a href="#Best-Practices-for-Java-Tool-Use" class="headerlink" title="Best Practices for Java Tool-Use"></a>Best Practices for Java Tool-Use</h2><h3 id="1-Type-Safety-with-Java"><a href="#1-Type-Safety-with-Java" class="headerlink" title="1. Type Safety with Java"></a>1. Type Safety with Java</h3><p>Java’s strong typing is a superpower here. Define clear interfaces for your tools:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">Tool</span> &#123;</span><br><span class="line">    String <span class="title function_">getName</span><span class="params">()</span>;</span><br><span class="line">    String <span class="title function_">getDescription</span><span class="params">()</span>;</span><br><span class="line">    Object <span class="title function_">execute</span><span class="params">(Map&lt;String, Object&gt; arguments)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-Error-Handling"><a href="#2-Error-Handling" class="headerlink" title="2. Error Handling"></a>2. Error Handling</h3><p>Always handle exceptions in tool execution. A failed tool call shouldn’t crash the agent.</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Tool(description = &quot;Fetch user data&quot;)</span></span><br><span class="line"><span class="keyword">public</span> String <span class="title function_">getUser</span><span class="params">(<span class="meta">@P(&quot;userId&quot;)</span> String userId)</span> &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userService.findById(userId);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;Error: &quot;</span> + e.getMessage();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-Security-Considerations"><a href="#3-Security-Considerations" class="headerlink" title="3. Security Considerations"></a>3. Security Considerations</h3><ul><li><strong>Validate inputs</strong>: LLMs can be tricked into passing malicious arguments.</li><li><strong>Restrict tools</strong>: Only expose safe, read-only tools in production.</li><li><strong>Audit calls</strong>: Log all tool invocations for compliance.</li></ul><h3 id="4-Performance"><a href="#4-Performance" class="headerlink" title="4. Performance"></a>4. Performance</h3><p>Tool calls add latency. Cache results when possible and use async execution for independent tools.</p><h2 id="When-to-Use-Which-Framework"><a href="#When-to-Use-Which-Framework" class="headerlink" title="When to Use Which Framework"></a>When to Use Which Framework</h2><table><thead><tr><th>Use Case</th><th>Recommended Framework</th></tr></thead><tbody><tr><td>Spring Boot project</td><td>Spring AI</td></tr><tr><td>Need maximum flexibility</td><td>LangChain4j</td></tr><tr><td>Simple function calling</td><td>Either</td></tr><tr><td>Complex agent workflows</td><td>LangChain4j (built-in agent)</td></tr><tr><td>Integration with Spring ecosystem</td><td>Spring AI</td></tr><tr><td>Microservices architecture</td><td>LangChain4j</td></tr></tbody></table><h2 id="Real-World-Example-Customer-Support-Agent"><a href="#Real-World-Example-Customer-Support-Agent" class="headerlink" title="Real-World Example: Customer Support Agent"></a>Real-World Example: Customer Support Agent</h2><p>Let’s build a practical example—a customer support agent that can:</p><ol><li>Look up order status</li><li>Process refunds</li><li>Escalate to a human</li></ol><h3 id="Using-Spring-AI"><a href="#Using-Spring-AI" class="headerlink" title="Using Spring AI"></a>Using Spring AI</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CustomerSupportTools</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Function</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getOrderStatus</span><span class="params">(<span class="meta">@Description(&quot;Order ID&quot;)</span> String orderId)</span> &#123;</span><br><span class="line">        <span class="type">Order</span> <span class="variable">order</span> <span class="operator">=</span> orderService.findById(orderId);</span><br><span class="line">        <span class="keyword">return</span> order.getStatus().toString();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Function</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">processRefund</span><span class="params">(<span class="meta">@Description(&quot;Order ID&quot;)</span> String orderId,</span></span><br><span class="line"><span class="params">                                <span class="meta">@Description(&quot;Reason&quot;)</span> String reason)</span> &#123;</span><br><span class="line">        refundService.process(orderId, reason);</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;Refund processed for order &quot;</span> + orderId;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Function</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">escalate</span><span class="params">(<span class="meta">@Description(&quot;Reason for escalation&quot;)</span> String reason)</span> &#123;</span><br><span class="line">        escalationService.createTicket(reason);</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;Escalated to human agent&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Using-LangChain4j"><a href="#Using-LangChain4j" class="headerlink" title="Using LangChain4j"></a>Using LangChain4j</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Tool(description = &quot;Look up order status&quot;)</span></span><br><span class="line"><span class="keyword">public</span> String <span class="title function_">getOrderStatus</span><span class="params">(<span class="meta">@P(&quot;Order ID&quot;)</span> String orderId)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> orderService.findById(orderId).getStatus().toString();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Tool(description = &quot;Process a refund&quot;)</span></span><br><span class="line"><span class="keyword">public</span> String <span class="title function_">processRefund</span><span class="params">(<span class="meta">@P(&quot;Order ID&quot;)</span> String orderId,</span></span><br><span class="line"><span class="params">                            <span class="meta">@P(&quot;Reason&quot;)</span> String reason)</span> &#123;</span><br><span class="line">    refundService.process(orderId, reason);</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;Refund processed&quot;</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Both approaches yield the same result: an LLM that can interact with your business logic.</p><h2 id="The-Future-of-Java-AI-Development"><a href="#The-Future-of-Java-AI-Development" class="headerlink" title="The Future of Java AI Development"></a>The Future of Java AI Development</h2><p>The Java ecosystem is catching up rapidly. With projects like <strong>Spring AI</strong> gaining traction and <strong>LangChain4j</strong> maturing, Java developers now have first-class support for building AI-powered applications.</p><p>Key trends to watch:</p><ul><li><strong>Multi-modal agents</strong>: Tools that can process images, audio, and video</li><li><strong>RAG (Retrieval-Augmented Generation)</strong>: Integrating vector databases for knowledge retrieval</li><li><strong>Agent-to-agent communication</strong>: Agents that can collaborate</li><li><strong>Local LLM support</strong>: Running models on-premise for privacy</li></ul><h2 id="Key-Takeaways"><a href="#Key-Takeaways" class="headerlink" title="Key Takeaways"></a>Key Takeaways</h2><ul><li><strong>Tool-use</strong> bridges the gap between LLM reasoning and real-world data&#x2F;actions</li><li><strong>Spring AI</strong> integrates seamlessly with Spring Boot, ideal for enterprise Java</li><li><strong>LangChain4j</strong> offers a flexible, modular approach with built-in agent support</li><li><strong>Function calling</strong> lets LLMs request data from your Java services</li><li><strong>Agents</strong> use function calling in loops to solve complex, multi-step tasks</li><li>Always prioritize <strong>type safety</strong>, <strong>error handling</strong>, and <strong>security</strong> in tool implementations</li><li>The Java AI ecosystem is maturing rapidly—now is the time to start building</li></ul><p>Whether you’re enhancing an existing application with AI features or building a new agent-driven platform, Java now has the tools to compete with Python in the AI space. Start small with function calling, then evolve to agents as your requirements grow.</p>]]>
    </content>
    <id>https://thoughtfly.github.io/devtech/2026/09/11/tool-use-frameworks-for-java-from-function-calling-to-agents/</id>
    <link href="https://thoughtfly.github.io/devtech/2026/09/11/tool-use-frameworks-for-java-from-function-calling-to-agents/"/>
    <published>2026-09-11T16:00:00.000Z</published>
    <summary>Learn how to build LLM-powered agents in Java using tool-use frameworks, function calling, and practical code examples.</summary>
    <title>Tool-Use Frameworks for Java: From Function Calling to Agents</title>
    <updated>2026-09-21T14:46:52.851Z</updated>
  </entry>
  <entry>
    <author>
      <name>DevTech Team</name>
    </author>
    <category term="Java" scheme="https://thoughtfly.github.io/devtech/categories/Java/"/>
    <category term="Java" scheme="https://thoughtfly.github.io/devtech/tags/Java/"/>
    <category term="RAG" scheme="https://thoughtfly.github.io/devtech/tags/RAG/"/>
    <category term="LLM" scheme="https://thoughtfly.github.io/devtech/tags/LLM/"/>
    <category term="AI Agents" scheme="https://thoughtfly.github.io/devtech/tags/AI-Agents/"/>
    <category term="Memory Architecture" scheme="https://thoughtfly.github.io/devtech/tags/Memory-Architecture/"/>
    <category term="Vector Databases" scheme="https://thoughtfly.github.io/devtech/tags/Vector-Databases/"/>
    <content>
      <![CDATA[<h2 id="The-Missing-Piece-in-Autonomous-Agents"><a href="#The-Missing-Piece-in-Autonomous-Agents" class="headerlink" title="The Missing Piece in Autonomous Agents"></a>The Missing Piece in Autonomous Agents</h2><p>When we first started building AI agents in production, we assumed that a large context window was enough. We fed the entire conversation history into the prompt, attached a few documents via RAG, and called it a day. It worked—until it didn’t.</p><p>As conversations stretched beyond 50–100 turns, latency spiked. Token costs ballooned. And worse, the agent began to forget critical details from the beginning of the session or, even more concerning, failed to retain user preferences across separate interactions.</p><p>The problem wasn’t the model’s intelligence. It was its memory architecture. Just like humans, AI agents need different types of memory to function effectively: short-term working memory for immediate tasks, long-term semantic memory for knowledge, and episodic memory for personal experiences.</p><p>In this post, we’ll dive deep into these three memory architectures and show you how to implement them using Java, Spring AI, and modern vector databases.</p><h2 id="Why-Memory-Matters-in-Agent-Design"><a href="#Why-Memory-Matters-in-Agent-Design" class="headerlink" title="Why Memory Matters in Agent Design"></a>Why Memory Matters in Agent Design</h2><p>Before we architect anything, let’s understand why memory is a first-class citizen in agent systems. Consider these scenarios:</p><ol><li><strong>Context Window Limits</strong>: Even with 128K+ token contexts, there’s a hard limit. Once you hit it, you must truncate or summarize, risking information loss.</li><li><strong>Cost Efficiency</strong>: Every token in a prompt costs money. Storing and retrieving only relevant memories reduces inference costs significantly.</li><li><strong>Personalization</strong>: Users expect agents to remember their preferences, past interactions, and learned behaviors across sessions.</li><li><strong>Reasoning Quality</strong>: Agents that can recall specific past events (episodic memory) make better decisions than those starting from scratch every time.</li></ol><p>The solution isn’t to rely solely on the LLM’s context window. It’s to build a structured memory system that mirrors how humans organize information.</p><h2 id="The-Three-Tier-Memory-Model"><a href="#The-Three-Tier-Memory-Model" class="headerlink" title="The Three-Tier Memory Model"></a>The Three-Tier Memory Model</h2><p>Let’s define the three memory types we’ll implement:</p><h3 id="1-Short-Term-Memory-Working-Memory"><a href="#1-Short-Term-Memory-Working-Memory" class="headerlink" title="1. Short-Term Memory (Working Memory)"></a>1. Short-Term Memory (Working Memory)</h3><p>Short-term memory holds the immediate context of the current interaction. It’s analogous to your working memory when solving a problem right now. For an AI agent, this includes:</p><ul><li>The current conversation turn</li><li>Recent tool outputs</li><li>Active goals and sub-goals</li><li>Temporary variables and intermediate results</li></ul><p><strong>Characteristics:</strong></p><ul><li>High volatility: Forgotten after the session ends (or summarized)</li><li>Fast access: Available in the prompt context</li><li>Limited capacity: Constrained by the LLM’s context window</li></ul><h3 id="2-Long-Term-Memory-Semantic-Memory"><a href="#2-Long-Term-Memory-Semantic-Memory" class="headerlink" title="2. Long-Term Memory (Semantic Memory)"></a>2. Long-Term Memory (Semantic Memory)</h3><p>Long-term memory stores general knowledge, facts, and learned concepts. This is the agent’s encyclopedia. It includes:</p><ul><li>Domain knowledge (e.g., company policies, technical documentation)</li><li>User preferences and profiles</li><li>Frequently accessed facts</li><li>Procedural knowledge (how to do things)</li></ul><p><strong>Characteristics:</strong></p><ul><li>Durable: Persists across sessions</li><li>Sparse: Only the most relevant information is retrieved</li><li>Indexed: Stored in vector databases for semantic search</li></ul><h3 id="3-Episodic-Memory-Autobiographical-Memory"><a href="#3-Episodic-Memory-Autobiographical-Memory" class="headerlink" title="3. Episodic Memory (Autobiographical Memory)"></a>3. Episodic Memory (Autobiographical Memory)</h3><p>Episodic memory stores specific experiences and events. This is the agent’s diary. It includes:</p><ul><li>Past conversations and interactions</li><li>Decisions made and their outcomes</li><li>User feedback and corrections</li><li>Specific incidents that shaped behavior</li></ul><p><strong>Characteristics:</strong></p><ul><li>Time-stamped: Ordered chronologically</li><li>Rich: Contains full context of events</li><li>Retrieval-based: Accessed when relevant to current tasks</li></ul><h2 id="Architecture-Overview"><a href="#Architecture-Overview" class="headerlink" title="Architecture Overview"></a>Architecture Overview</h2><p>Here’s how these memory layers interact in a typical agent system:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">┌─────────────────────────────────────────────────────────┐</span><br><span class="line">│                    Agent Core                           │</span><br><span class="line">│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐    │</span><br><span class="line">│  │   Short     │  │   Long      │  │   Episodic  │    │</span><br><span class="line">│  │   Term      │  │   Term      │  │   Memory    │    │</span><br><span class="line">│  │   Memory    │  │   Memory    │  │             │    │</span><br><span class="line">│  └──────┬──────┘  └──────┬──────┘  └──────┬──────┘    │</span><br><span class="line">│         │                │                │           │</span><br><span class="line">│         └────────────────┴────────────────┘           │</span><br><span class="line">│                        │                              │</span><br><span class="line">│              ┌─────────▼─────────┐                    │</span><br><span class="line">│              │   Memory Router    │                    │</span><br><span class="line">│              │  (Decision Engine) │                    │</span><br><span class="line">│              └─────────┬─────────┘                    │</span><br><span class="line">└────────────────────────┼──────────────────────────────┘</span><br><span class="line">                         │</span><br><span class="line">        ┌────────────────┼────────────────┐</span><br><span class="line">        │                │                │</span><br><span class="line">   ┌────▼────┐     ┌────▼────┐     ┌────▼────┐</span><br><span class="line">   │ In-Memory│    │ Vector  │     │ Event   │</span><br><span class="line">   │ Cache   │     │ DB      │     │ Store   │</span><br><span class="line">   └─────────┘     └─────────┘     └─────────┘</span><br></pre></td></tr></table></figure><p>The Memory Router decides which memory store to query based on the current task. Short-term memory is always in the prompt. Long-term memory is retrieved via semantic search. Episodic memory is fetched when historical context is needed.</p><h2 id="Implementing-Short-Term-Memory"><a href="#Implementing-Short-Term-Memory" class="headerlink" title="Implementing Short-Term Memory"></a>Implementing Short-Term Memory</h2><p>Short-term memory is the simplest layer. In Java, we can use a thread-local or session-scoped storage to maintain the conversation context.</p><h3 id="The-Conversation-Buffer"><a href="#The-Conversation-Buffer" class="headerlink" title="The Conversation Buffer"></a>The Conversation Buffer</h3><p>We’ll create a <code>ConversationMemory</code> class that acts as a sliding window over recent messages:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="meta">@Scope(&quot;prototype&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ConversationMemory</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Deque&lt;Message&gt; history = <span class="keyword">new</span> <span class="title class_">ArrayDeque</span>&lt;&gt;();</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">int</span> maxTurns;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">ConversationMemory</span><span class="params">(<span class="type">int</span> maxTurns)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.maxTurns = maxTurns;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">addAssistantMessage</span><span class="params">(String content)</span> &#123;</span><br><span class="line">        history.addLast(<span class="keyword">new</span> <span class="title class_">Message</span>(Message.Role.ASSISTANT, content));</span><br><span class="line">        trimIfNecessary();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">addUserMessage</span><span class="params">(String content)</span> &#123;</span><br><span class="line">        history.addLast(<span class="keyword">new</span> <span class="title class_">Message</span>(Message.Role.USER, content));</span><br><span class="line">        trimIfNecessary();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">addToolResult</span><span class="params">(String toolName, Object result)</span> &#123;</span><br><span class="line">        history.addLast(<span class="keyword">new</span> <span class="title class_">Message</span>(</span><br><span class="line">            Message.Role.TOOL, </span><br><span class="line">            String.format(<span class="string">&quot;Tool &#x27;%s&#x27; returned: %s&quot;</span>, toolName, result)</span><br><span class="line">        ));</span><br><span class="line">        trimIfNecessary();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">trimIfNecessary</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">while</span> (history.size() &gt; maxTurns * <span class="number">3</span>) &#123; <span class="comment">// 3 messages per turn (user, tool, assistant)</span></span><br><span class="line">            history.removeFirst();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> List&lt;Message&gt; <span class="title function_">getRecentMessages</span><span class="params">(<span class="type">int</span> n)</span> &#123;</span><br><span class="line">        List&lt;Message&gt; recent = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">        Iterator&lt;Message&gt; it = history.descendingIterator();</span><br><span class="line">        <span class="keyword">while</span> (it.hasNext() &amp;&amp; recent.size() &lt; n) &#123;</span><br><span class="line">            recent.add(it.next());</span><br><span class="line">        &#125;</span><br><span class="line">        Collections.reverse(recent);</span><br><span class="line">        <span class="keyword">return</span> recent;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> List&lt;Message&gt; <span class="title function_">getFullHistory</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(history);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>This class maintains a bounded queue of messages. By limiting the window size, we ensure that the prompt never exceeds token limits. The <code>trimIfNecessary()</code> method removes oldest messages when the buffer is full.</p><h3 id="Integration-with-Spring-AI"><a href="#Integration-with-Spring-AI" class="headerlink" title="Integration with Spring AI"></a>Integration with Spring AI</h3><p>Spring AI’s <code>ChatClient</code> works seamlessly with this memory class:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AgentService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> ChatClient chatClient;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> ConversationMemory memory;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">AgentService</span><span class="params">(ChatClient.Builder builder, ConversationMemory memory)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.chatClient = builder.build();</span><br><span class="line">        <span class="built_in">this</span>.memory = memory;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">processRequest</span><span class="params">(String userMessage)</span> &#123;</span><br><span class="line">        memory.addUserMessage(userMessage);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// Build prompt with recent context</span></span><br><span class="line">        List&lt;Message&gt; recentContext = memory.getRecentMessages(<span class="number">10</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="type">Prompt</span> <span class="variable">prompt</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Prompt</span>(</span><br><span class="line">            recentContext.stream()</span><br><span class="line">                .map(m -&gt; <span class="keyword">new</span> <span class="title class_">Message</span>(m.getRole().toString(), m.getContent()))</span><br><span class="line">                .toList()</span><br><span class="line">        );</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// Call LLM</span></span><br><span class="line">        <span class="type">ChatResponse</span> <span class="variable">response</span> <span class="operator">=</span> chatClient.call(prompt);</span><br><span class="line">        <span class="type">String</span> <span class="variable">assistantReply</span> <span class="operator">=</span> response.getResults().get(<span class="number">0</span>).getOutput().getText();</span><br><span class="line">        </span><br><span class="line">        memory.addAssistantMessage(assistantReply);</span><br><span class="line">        <span class="keyword">return</span> assistantReply;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>The key insight here is that short-term memory is <strong>ephemeral</strong>. It exists only for the duration of the conversation and is discarded afterward. This is intentional—short-term memory is meant for immediate reasoning, not permanent storage.</p><h2 id="Implementing-Long-Term-Memory"><a href="#Implementing-Long-Term-Memory" class="headerlink" title="Implementing Long-Term Memory"></a>Implementing Long-Term Memory</h2><p>Long-term memory requires persistent storage and semantic search capabilities. We’ll use a vector database to store embeddings of important facts and retrieve them based on relevance.</p><h3 id="Choosing-a-Vector-Database"><a href="#Choosing-a-Vector-Database" class="headerlink" title="Choosing a Vector Database"></a>Choosing a Vector Database</h3><p>For production Java applications, we recommend:</p><ul><li><strong>PostgreSQL with pgvector</strong>: Great for relational data with vector search</li><li><strong>Redis with RediSearch</strong>: Fast in-memory vector search</li><li><strong>Pinecone</strong>: Managed service, excellent for scaling</li></ul><p>We’ll use PostgreSQL with pgvector in our examples, as it’s widely adopted in enterprise Java stacks.</p><h3 id="Storing-Semantic-Knowledge"><a href="#Storing-Semantic-Knowledge" class="headerlink" title="Storing Semantic Knowledge"></a>Storing Semantic Knowledge</h3><p>First, let’s create a schema for our long-term memory:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> EXTENSION vector;</span><br><span class="line"></span><br><span class="line"><span class="keyword">CREATE TABLE</span> semantic_memory (</span><br><span class="line">    id SERIAL <span class="keyword">PRIMARY KEY</span>,</span><br><span class="line">    content TEXT <span class="keyword">NOT NULL</span>,</span><br><span class="line">    embedding vector(<span class="number">1536</span>), <span class="comment">-- OpenAI ada-002 dimension</span></span><br><span class="line">    metadata JSONB,</span><br><span class="line">    created_at <span class="type">TIMESTAMP</span> <span class="keyword">DEFAULT</span> <span class="built_in">CURRENT_TIMESTAMP</span>,</span><br><span class="line">    updated_at <span class="type">TIMESTAMP</span> <span class="keyword">DEFAULT</span> <span class="built_in">CURRENT_TIMESTAMP</span></span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="keyword">CREATE</span> INDEX <span class="keyword">ON</span> semantic_memory <span class="keyword">USING</span> ivfflat (embedding vector_cosine_ops);</span><br></pre></td></tr></table></figure><p>Now, let’s create a service to manage this memory:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LongTermMemoryService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> JdbcTemplate jdbcTemplate;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> EmbeddingClient embeddingClient;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">dimension</span> <span class="operator">=</span> <span class="number">1536</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">LongTermMemoryService</span><span class="params">(JdbcTemplate jdbcTemplate, </span></span><br><span class="line"><span class="params">                                 EmbeddingClient embeddingClient)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.jdbcTemplate = jdbcTemplate;</span><br><span class="line">        <span class="built_in">this</span>.embeddingClient = embeddingClient;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">storeFact</span><span class="params">(String content, Map&lt;String, Object&gt; metadata)</span> &#123;</span><br><span class="line">        <span class="type">float</span>[] embedding = embeddingClient.embed(content);</span><br><span class="line">        <span class="type">String</span> <span class="variable">embeddingJson</span> <span class="operator">=</span> Arrays.toString(embedding);</span><br><span class="line">        </span><br><span class="line">        <span class="type">String</span> <span class="variable">sql</span> <span class="operator">=</span> <span class="string">&quot;INSERT INTO semantic_memory (content, embedding, metadata) &quot;</span> +</span><br><span class="line">                     <span class="string">&quot;VALUES (?, ?, ?)&quot;</span>;</span><br><span class="line">        </span><br><span class="line">        jdbcTemplate.update(sql, content, embeddingJson, </span><br><span class="line">            JsonUtils.toJson(metadata));</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> List&lt;MemoryItem&gt; <span class="title function_">retrieveRelevant</span><span class="params">(String query, <span class="type">int</span> topK)</span> &#123;</span><br><span class="line">        <span class="type">float</span>[] queryEmbedding = embeddingClient.embed(query);</span><br><span class="line">        <span class="type">String</span> <span class="variable">queryVector</span> <span class="operator">=</span> Arrays.toString(queryEmbedding);</span><br><span class="line">        </span><br><span class="line">        <span class="type">String</span> <span class="variable">sql</span> <span class="operator">=</span> <span class="string">&quot;SELECT content, metadata, 1 - (embedding &lt;=&gt; ?) AS similarity &quot;</span> +</span><br><span class="line">                     <span class="string">&quot;FROM semantic_memory &quot;</span> +</span><br><span class="line">                     <span class="string">&quot;ORDER BY embedding &lt;=&gt; ? &quot;</span> +</span><br><span class="line">                     <span class="string">&quot;LIMIT ?&quot;</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> jdbcTemplate.query(sql, (rs, rowNum) -&gt; &#123;</span><br><span class="line">            <span class="type">MemoryItem</span> <span class="variable">item</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MemoryItem</span>();</span><br><span class="line">            item.setContent(rs.getString(<span class="string">&quot;content&quot;</span>));</span><br><span class="line">            item.setMetadata(JsonUtils.fromJson(rs.getString(<span class="string">&quot;metadata&quot;</span>)));</span><br><span class="line">            item.setSimilarity(rs.getDouble(<span class="string">&quot;similarity&quot;</span>));</span><br><span class="line">            <span class="keyword">return</span> item;</span><br><span class="line">        &#125;, queryVector, queryVector, topK);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="When-to-Store-in-Long-Term-Memory"><a href="#When-to-Store-in-Long-Term-Memory" class="headerlink" title="When to Store in Long-Term Memory"></a>When to Store in Long-Term Memory</h3><p>Not everything should be stored. We need a filtering mechanism. Here’s a strategy:</p><ol><li><strong>User Preferences</strong>: Always store (e.g., “I prefer concise answers”)</li><li><strong>Domain Facts</strong>: Store if they’re generalizable (e.g., “The API endpoint is &#x2F;v2&#x2F;data”)</li><li><strong>Decisions</strong>: Store high-impact decisions with reasoning</li><li><strong>Tool Patterns</strong>: Store successful tool usage patterns</li></ol><p>We can implement an automatic extraction service:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MemoryExtractorService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> ChatClient chatClient;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> LongTermMemoryService longTermMemory;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">extractAndStore</span><span class="params">(UserMessage userMsg, AssistantMessage assistantMsg)</span> &#123;</span><br><span class="line">        <span class="comment">// Ask LLM to identify memorable facts</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">extractionPrompt</span> <span class="operator">=</span> <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">            Extract any important facts, preferences, or knowledge from this </span></span><br><span class="line"><span class="string">            conversation that should be remembered long-term. Return as JSON:</span></span><br><span class="line"><span class="string">            &#123;</span></span><br><span class="line"><span class="string">              &quot;facts&quot;: [&quot;string&quot;],</span></span><br><span class="line"><span class="string">              &quot;preferences&quot;: [&quot;string&quot;],</span></span><br><span class="line"><span class="string">              &quot;decisions&quot;: [&quot;string&quot;]</span></span><br><span class="line"><span class="string">            &#125;</span></span><br><span class="line"><span class="string">            &quot;&quot;&quot;</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="type">ChatResponse</span> <span class="variable">response</span> <span class="operator">=</span> chatClient.call(</span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">Prompt</span>(extractionPrompt + <span class="string">&quot;\n\nConversation:\n&quot;</span> + </span><br><span class="line">                       userMsg.getContent() + <span class="string">&quot;\n&quot;</span> + assistantMsg.getContent())</span><br><span class="line">        );</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// Parse and store</span></span><br><span class="line">        <span class="type">MemoryExtraction</span> <span class="variable">extraction</span> <span class="operator">=</span> JsonUtils.fromJson(</span><br><span class="line">            response.getResults().get(<span class="number">0</span>).getOutput().getText()</span><br><span class="line">        );</span><br><span class="line">        </span><br><span class="line">        extraction.getFacts().forEach(fact -&gt; </span><br><span class="line">            longTermMemory.storeFact(fact, Map.of(<span class="string">&quot;type&quot;</span>, <span class="string">&quot;fact&quot;</span>))</span><br><span class="line">        );</span><br><span class="line">        extraction.getPreferences().forEach(pref -&gt; </span><br><span class="line">            longTermMemory.storeFact(pref, Map.of(<span class="string">&quot;type&quot;</span>, <span class="string">&quot;preference&quot;</span>))</span><br><span class="line">        );</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>This approach lets the LLM decide what’s worth remembering, reducing noise in the long-term memory store.</p><h2 id="Implementing-Episodic-Memory"><a href="#Implementing-Episodic-Memory" class="headerlink" title="Implementing Episodic Memory"></a>Implementing Episodic Memory</h2><p>Episodic memory is the most complex layer. It needs to store events with rich context, time stamps, and relationships. Unlike semantic memory (which stores facts), episodic memory stores experiences.</p><h3 id="Schema-Design"><a href="#Schema-Design" class="headerlink" title="Schema Design"></a>Schema Design</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> episodic_memory (</span><br><span class="line">    id SERIAL <span class="keyword">PRIMARY KEY</span>,</span><br><span class="line">    event_type <span class="type">VARCHAR</span>(<span class="number">50</span>) <span class="keyword">NOT NULL</span>, <span class="comment">-- &quot;conversation&quot;, &quot;tool_call&quot;, &quot;decision&quot;</span></span><br><span class="line">    <span class="type">timestamp</span> <span class="type">TIMESTAMP</span> <span class="keyword">DEFAULT</span> <span class="built_in">CURRENT_TIMESTAMP</span>,</span><br><span class="line">    user_id <span class="type">VARCHAR</span>(<span class="number">100</span>),</span><br><span class="line">    session_id <span class="type">VARCHAR</span>(<span class="number">100</span>),</span><br><span class="line">    context JSONB, <span class="comment">-- Full conversation snapshot</span></span><br><span class="line">    summary TEXT, <span class="comment">-- LLM-generated summary for quick retrieval</span></span><br><span class="line">    embedding vector(<span class="number">1536</span>),</span><br><span class="line">    related_event_ids <span class="type">INTEGER</span>[]</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="keyword">CREATE</span> INDEX <span class="keyword">ON</span> episodic_memory <span class="keyword">USING</span> ivfflat (embedding vector_cosine_ops);</span><br><span class="line"><span class="keyword">CREATE</span> INDEX <span class="keyword">ON</span> episodic_memory (user_id, <span class="type">timestamp</span> <span class="keyword">DESC</span>);</span><br></pre></td></tr></table></figure><h3 id="Storing-Episodes"><a href="#Storing-Episodes" class="headerlink" title="Storing Episodes"></a>Storing Episodes</h3><p>We’ll create an <code>EpisodicMemoryService</code> that captures complete interaction episodes:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">EpisodicMemoryService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> JdbcTemplate jdbcTemplate;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> EmbeddingClient embeddingClient;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> ChatClient chatClient;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">storeEpisode</span><span class="params">(String sessionId, String userId, </span></span><br><span class="line"><span class="params">                             List&lt;Message&gt; conversation, </span></span><br><span class="line"><span class="params">                             List&lt;ToolCall&gt; toolCalls)</span> &#123;</span><br><span class="line">        <span class="comment">// Generate a summary for quick retrieval</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">summary</span> <span class="operator">=</span> generateSummary(conversation);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// Store full context</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">contextJson</span> <span class="operator">=</span> JsonUtils.toJson(Map.of(</span><br><span class="line">            <span class="string">&quot;messages&quot;</span>, conversation,</span><br><span class="line">            <span class="string">&quot;toolCalls&quot;</span>, toolCalls,</span><br><span class="line">            <span class="string">&quot;timestamp&quot;</span>, Instant.now().toString()</span><br><span class="line">        ));</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// Generate embedding for semantic search</span></span><br><span class="line">        <span class="type">float</span>[] embedding = embeddingClient.embed(summary);</span><br><span class="line">        <span class="type">String</span> <span class="variable">embeddingJson</span> <span class="operator">=</span> Arrays.toString(embedding);</span><br><span class="line">        </span><br><span class="line">        <span class="type">String</span> <span class="variable">sql</span> <span class="operator">=</span> <span class="string">&quot;INSERT INTO episodic_memory &quot;</span> +</span><br><span class="line">                     <span class="string">&quot;(event_type, user_id, session_id, context, summary, embedding) &quot;</span> +</span><br><span class="line">                     <span class="string">&quot;VALUES (&#x27;conversation&#x27;, ?, ?, ?, ?, ?)&quot;</span>;</span><br><span class="line">        </span><br><span class="line">        jdbcTemplate.update(sql, userId, sessionId, contextJson, summary, embeddingJson);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> String <span class="title function_">generateSummary</span><span class="params">(List&lt;Message&gt; conversation)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">conversationText</span> <span class="operator">=</span> conversation.stream()</span><br><span class="line">            .map(m -&gt; m.getRole() + <span class="string">&quot;: &quot;</span> + m.getContent())</span><br><span class="line">            .collect(Collectors.joining(<span class="string">&quot;\n&quot;</span>));</span><br><span class="line">        </span><br><span class="line">        <span class="type">String</span> <span class="variable">prompt</span> <span class="operator">=</span> <span class="string">&quot;Summarize this conversation in 2-3 sentences. &quot;</span> +</span><br><span class="line">                        <span class="string">&quot;Focus on key decisions, outcomes, and user intent.&quot;</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="type">ChatResponse</span> <span class="variable">response</span> <span class="operator">=</span> chatClient.call(<span class="keyword">new</span> <span class="title class_">Prompt</span>(prompt + <span class="string">&quot;\n\n&quot;</span> + conversationText));</span><br><span class="line">        <span class="keyword">return</span> response.getResults().get(<span class="number">0</span>).getOutput().getText();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> List&lt;EpisodicEvent&gt; <span class="title function_">retrieveRelated</span><span class="params">(String query, String userId, <span class="type">int</span> limit)</span> &#123;</span><br><span class="line">        <span class="type">float</span>[] queryEmbedding = embeddingClient.embed(query);</span><br><span class="line">        <span class="type">String</span> <span class="variable">queryVector</span> <span class="operator">=</span> Arrays.toString(queryEmbedding);</span><br><span class="line">        </span><br><span class="line">        <span class="type">String</span> <span class="variable">sql</span> <span class="operator">=</span> <span class="string">&quot;SELECT id, event_type, timestamp, summary, context &quot;</span> +</span><br><span class="line">                     <span class="string">&quot;FROM episodic_memory &quot;</span> +</span><br><span class="line">                     <span class="string">&quot;WHERE user_id = ? &quot;</span> +</span><br><span class="line">                     <span class="string">&quot;ORDER BY embedding &lt;=&gt; ? &quot;</span> +</span><br><span class="line">                     <span class="string">&quot;LIMIT ?&quot;</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> jdbcTemplate.query(sql, (rs, rowNum) -&gt; &#123;</span><br><span class="line">            <span class="type">EpisodicEvent</span> <span class="variable">event</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">EpisodicEvent</span>();</span><br><span class="line">            event.setId(rs.getInt(<span class="string">&quot;id&quot;</span>));</span><br><span class="line">            event.setEventType(rs.getString(<span class="string">&quot;event_type&quot;</span>));</span><br><span class="line">            event.setTimestamp(rs.getTimestamp(<span class="string">&quot;timestamp&quot;</span>));</span><br><span class="line">            event.setSummary(rs.getString(<span class="string">&quot;summary&quot;</span>));</span><br><span class="line">            event.setContext(JsonUtils.fromJson(rs.getString(<span class="string">&quot;context&quot;</span>)));</span><br><span class="line">            <span class="keyword">return</span> event;</span><br><span class="line">        &#125;, userId, queryVector, limit);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Retrieving-Episodic-Memory"><a href="#Retrieving-Episodic-Memory" class="headerlink" title="Retrieving Episodic Memory"></a>Retrieving Episodic Memory</h3><p>When should the agent query episodic memory? Here’s a decision framework:</p><ol><li><strong>User asks about past interactions</strong>: “What did we discuss last week?”</li><li><strong>Current task resembles a past task</strong>: “This looks like the bug we fixed before.”</li><li><strong>User mentions a previous incident</strong>: “Remember when the API failed?”</li><li><strong>Decision support needed</strong>: “What approach worked last time?”</li></ol><p>We can integrate this into the agent’s reasoning loop:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AgentReasoningService</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> EpisodicMemoryService episodicMemory;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> LongTermMemoryService longTermMemory;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> AgentContext <span class="title function_">enrichContext</span><span class="params">(String currentQuery, String userId)</span> &#123;</span><br><span class="line">        <span class="type">AgentContext</span> <span class="variable">context</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AgentContext</span>();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// Retrieve relevant episodic memories</span></span><br><span class="line">        List&lt;EpisodicEvent&gt; pastEpisodes = episodicMemory.retrieveRelated(</span><br><span class="line">            currentQuery, userId, <span class="number">3</span></span><br><span class="line">        );</span><br><span class="line">        context.setPastEpisodes(pastEpisodes);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// Retrieve relevant semantic memories</span></span><br><span class="line">        List&lt;MemoryItem&gt; relevantFacts = longTermMemory.retrieveRelevant(</span><br><span class="line">            currentQuery, <span class="number">5</span></span><br><span class="line">        );</span><br><span class="line">        context.setRelevantFacts(relevantFacts);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> context;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="The-Memory-Router-Deciding-What-to-Remember"><a href="#The-Memory-Router-Deciding-What-to-Remember" class="headerlink" title="The Memory Router: Deciding What to Remember"></a>The Memory Router: Deciding What to Remember</h2><p>The most critical component is the Memory Router. It decides:</p><ol><li>What to store in each memory type</li><li>When to retrieve from each type</li><li>How to combine memories into the prompt</li></ol><p>Here’s a simplified router implementation:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MemoryRouter</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> LongTermMemoryService longTermMemory;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> EpisodicMemoryService episodicMemory;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> ConversationMemory shortTermMemory;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> Prompt <span class="title function_">buildPrompt</span><span class="params">(String userMessage, String userId)</span> &#123;</span><br><span class="line">        List&lt;Message&gt; messages = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 1. System prompt with memory instructions</span></span><br><span class="line">        messages.add(<span class="keyword">new</span> <span class="title class_">Message</span>(Message.Role.SYSTEM, </span><br><span class="line">            buildSystemPrompt(userId)));</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 2. Retrieve relevant long-term memories</span></span><br><span class="line">        List&lt;MemoryItem&gt; semanticMemories = longTermMemory.retrieveRelevant(</span><br><span class="line">            userMessage, <span class="number">3</span></span><br><span class="line">        );</span><br><span class="line">        <span class="keyword">if</span> (!semanticMemories.isEmpty()) &#123;</span><br><span class="line">            messages.add(<span class="keyword">new</span> <span class="title class_">Message</span>(Message.Role.SYSTEM, </span><br><span class="line">                <span class="string">&quot;Relevant knowledge: &quot;</span> + formatMemories(semanticMemories)));</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 3. Retrieve relevant episodic memories</span></span><br><span class="line">        List&lt;EpisodicEvent&gt; episodicMemories = episodicMemory.retrieveRelated(</span><br><span class="line">            userMessage, userId, <span class="number">2</span></span><br><span class="line">        );</span><br><span class="line">        <span class="keyword">if</span> (!episodicMemories.isEmpty()) &#123;</span><br><span class="line">            messages.add(<span class="keyword">new</span> <span class="title class_">Message</span>(Message.Role.SYSTEM, </span><br><span class="line">                <span class="string">&quot;Past experiences: &quot;</span> + formatEpisodes(episodicMemories)));</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 4. Add short-term conversation history</span></span><br><span class="line">        messages.addAll(shortTermMemory.getRecentMessages(<span class="number">10</span>));</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 5. Add current user message</span></span><br><span class="line">        messages.add(<span class="keyword">new</span> <span class="title class_">Message</span>(Message.Role.USER, userMessage));</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Prompt</span>(messages);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> String <span class="title function_">buildSystemPrompt</span><span class="params">(String userId)</span> &#123;</span><br><span class="line">        <span class="comment">// Get user preferences from long-term memory</span></span><br><span class="line">        List&lt;MemoryItem&gt; preferences = longTermMemory.retrieveRelevant(</span><br><span class="line">            <span class="string">&quot;user preferences&quot;</span>, <span class="number">5</span></span><br><span class="line">        );</span><br><span class="line">        </span><br><span class="line">        <span class="type">String</span> <span class="variable">prefText</span> <span class="operator">=</span> preferences.isEmpty() ? <span class="string">&quot;&quot;</span> : </span><br><span class="line">            <span class="string">&quot;User preferences: &quot;</span> + formatMemories(preferences);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;You are a helpful AI assistant. &quot;</span> + prefText + </span><br><span class="line">               <span class="string">&quot;Use the provided context to give accurate, personalized responses.&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Performance-Considerations"><a href="#Performance-Considerations" class="headerlink" title="Performance Considerations"></a>Performance Considerations</h2><p>When implementing memory systems, keep these performance tips in mind:</p><h3 id="1-Caching-Layer"><a href="#1-Caching-Layer" class="headerlink" title="1. Caching Layer"></a>1. Caching Layer</h3><p>Add a caching layer (e.g., Caffeine or Redis) in front of your vector database. Most queries are repetitive, and caching avoids redundant embedding generation and database lookups.</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Cacheable(value = &quot;semanticMemory&quot;, key = &quot;#query + &#x27;-&#x27; + #topK&quot;)</span></span><br><span class="line"><span class="keyword">public</span> List&lt;MemoryItem&gt; <span class="title function_">getCachedRetrieval</span><span class="params">(String query, <span class="type">int</span> topK)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> longTermMemory.retrieveRelevant(query, topK);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-Asynchronous-Storage"><a href="#2-Asynchronous-Storage" class="headerlink" title="2. Asynchronous Storage"></a>2. Asynchronous Storage</h3><p>Don’t block the response path on memory writes. Use async processing:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Async</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">storeAsync</span><span class="params">(String content, Map&lt;String, Object&gt; metadata)</span> &#123;</span><br><span class="line">    longTermMemory.storeFact(content, metadata);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-Memory-Compaction"><a href="#3-Memory-Compaction" class="headerlink" title="3. Memory Compaction"></a>3. Memory Compaction</h3><p>Periodically summarize and compress old episodic memories. Merge related events and discard low-value episodes.</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Scheduled(cron = &quot;0 0 2 * * *&quot;)</span> <span class="comment">// Run daily at 2 AM</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">compactOldMemories</span><span class="params">()</span> &#123;</span><br><span class="line">    List&lt;EpisodicEvent&gt; oldEpisodes = episodicMemory.getOldEpisodes(<span class="number">30</span>);</span><br><span class="line">    <span class="keyword">for</span> (EpisodicEvent episode : oldEpisodes) &#123;</span><br><span class="line">        episodicMemory.summarizeAndMerge(episode);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-Embedding-Optimization"><a href="#4-Embedding-Optimization" class="headerlink" title="4. Embedding Optimization"></a>4. Embedding Optimization</h3><p>Reuse embeddings when possible. If the same content is stored multiple times, check for duplicates before generating new embeddings.</p><h2 id="Real-World-Example-Customer-Support-Agent"><a href="#Real-World-Example-Customer-Support-Agent" class="headerlink" title="Real-World Example: Customer Support Agent"></a>Real-World Example: Customer Support Agent</h2><p>Let’s see how these memory types work together in a customer support scenario.</p><h3 id="Scenario"><a href="#Scenario" class="headerlink" title="Scenario"></a>Scenario</h3><p>A user contacts support about a billing issue. The agent needs to:</p><ol><li>Remember the user’s account details (long-term)</li><li>Recall previous support tickets (episodic)</li><li>Maintain the current conversation flow (short-term)</li></ol><h3 id="Implementation"><a href="#Implementation" class="headerlink" title="Implementation"></a>Implementation</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CustomerSupportAgent</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> MemoryRouter memoryRouter;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> EpisodicMemoryService episodicMemory;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> LongTermMemoryService longTermMemory;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> ConversationMemory shortTermMemory;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">handleSupportRequest</span><span class="params">(String userId, String message)</span> &#123;</span><br><span class="line">        <span class="comment">// Store the interaction in episodic memory</span></span><br><span class="line">        episodicMemory.storeEpisode(</span><br><span class="line">            UUID.randomUUID().toString(), </span><br><span class="line">            userId, </span><br><span class="line">            shortTermMemory.getFullHistory(), </span><br><span class="line">            List.of()</span><br><span class="line">        );</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// Build enriched prompt</span></span><br><span class="line">        <span class="type">Prompt</span> <span class="variable">prompt</span> <span class="operator">=</span> memoryRouter.buildPrompt(message, userId);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// Generate response</span></span><br><span class="line">        <span class="type">ChatResponse</span> <span class="variable">response</span> <span class="operator">=</span> chatClient.call(prompt);</span><br><span class="line">        <span class="type">String</span> <span class="variable">reply</span> <span class="operator">=</span> response.getResults().get(<span class="number">0</span>).getOutput().getText();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// Extract and store new knowledge</span></span><br><span class="line">        memoryExtractor.extractAndStore(</span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">UserMessage</span>(message),</span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">AssistantMessage</span>(reply)</span><br><span class="line">        );</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> reply;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>In this example:</p><ul><li><strong>Short-term memory</strong> tracks the current billing conversation</li><li><strong>Long-term memory</strong> provides account details and billing policies</li><li><strong>Episodic memory</strong> recalls previous billing issues and resolutions</li></ul><p>The result is an agent that feels personalized and context-aware, rather than starting from scratch every time.</p><h2 id="Common-Pitfalls-and-Solutions"><a href="#Common-Pitfalls-and-Solutions" class="headerlink" title="Common Pitfalls and Solutions"></a>Common Pitfalls and Solutions</h2><h3 id="Pitfall-1-Memory-Overload"><a href="#Pitfall-1-Memory-Overload" class="headerlink" title="Pitfall 1: Memory Overload"></a>Pitfall 1: Memory Overload</h3><p><strong>Problem</strong>: Storing too much information makes retrieval noisy and slow.</p><p><strong>Solution</strong>: Implement relevance scoring and threshold-based filtering. Only store memories above a confidence threshold. Use summarization to compress low-value episodes.</p><h3 id="Pitfall-2-Stale-Memories"><a href="#Pitfall-2-Stale-Memories" class="headerlink" title="Pitfall 2: Stale Memories"></a>Pitfall 2: Stale Memories</h3><p><strong>Problem</strong>: Old information becomes inaccurate but persists in the store.</p><p><strong>Solution</strong>: Add expiration timestamps and update mechanisms. When storing, check for existing similar memories and update them instead of creating duplicates.</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">storeOrUpdateFact</span><span class="params">(String content, Map&lt;String, Object&gt; metadata)</span> &#123;</span><br><span class="line">    <span class="comment">// Check for similar existing memories</span></span><br><span class="line">    List&lt;MemoryItem&gt; similar = longTermMemory.retrieveRelevant(content, <span class="number">1</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (!similar.isEmpty() &amp;&amp; similar.get(<span class="number">0</span>).getSimilarity() &gt; <span class="number">0.85</span>) &#123;</span><br><span class="line">        <span class="comment">// Update existing memory</span></span><br><span class="line">        longTermMemory.update(similar.get(<span class="number">0</span>).getId(), content, metadata);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// Store new memory</span></span><br><span class="line">        longTermMemory.storeFact(content, metadata);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Pitfall-3-Context-Window-Bloat"><a href="#Pitfall-3-Context-Window-Bloat" class="headerlink" title="Pitfall 3: Context Window Bloat"></a>Pitfall 3: Context Window Bloat</h3><p><strong>Problem</strong>: Retrieving too many memories exceeds the context window.</p><p><strong>Solution</strong>: Implement a budgeting system. Allocate token budgets to each memory type and truncate as needed.</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MemoryBudget</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">SHORT_TERM_BUDGET</span> <span class="operator">=</span> <span class="number">4000</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">LONG_TERM_BUDGET</span> <span class="operator">=</span> <span class="number">2000</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">EPISODIC_BUDGET</span> <span class="operator">=</span> <span class="number">1000</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// Truncate memories to fit budget</span></span><br><span class="line">    <span class="keyword">public</span> List&lt;Message&gt; <span class="title function_">truncateToFitBudget</span><span class="params">(List&lt;Message&gt; memories, <span class="type">int</span> budget)</span> &#123;</span><br><span class="line">        <span class="comment">// Implementation using token counting</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Testing-Your-Memory-System"><a href="#Testing-Your-Memory-System" class="headerlink" title="Testing Your Memory System"></a>Testing Your Memory System</h2><p>Don’t forget to test your memory implementation. Here’s a simple test strategy:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootTest</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">MemorySystemTest</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> LongTermMemoryService longTermMemory;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> EpisodicMemoryService episodicMemory;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">testLongTermMemoryRetrieval</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// Store a fact</span></span><br><span class="line">        longTermMemory.storeFact(<span class="string">&quot;The API rate limit is 100 requests per minute&quot;</span>, </span><br><span class="line">            Map.of(<span class="string">&quot;type&quot;</span>, <span class="string">&quot;fact&quot;</span>));</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// Retrieve it</span></span><br><span class="line">        List&lt;MemoryItem&gt; results = longTermMemory.retrieveRelevant(</span><br><span class="line">            <span class="string">&quot;What is the API rate limit?&quot;</span>, <span class="number">3</span></span><br><span class="line">        );</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// Verify</span></span><br><span class="line">        assertThat(results).isNotEmpty();</span><br><span class="line">        assertThat(results.get(<span class="number">0</span>).getContent())</span><br><span class="line">            .contains(<span class="string">&quot;rate limit&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">testEpisodicMemoryChronology</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// Store two episodes</span></span><br><span class="line">        episodicMemory.storeEpisode(<span class="string">&quot;session1&quot;</span>, <span class="string">&quot;user1&quot;</span>, </span><br><span class="line">            List.of(<span class="keyword">new</span> <span class="title class_">Message</span>(Role.USER, <span class="string">&quot;First message&quot;</span>)), List.of());</span><br><span class="line">        episodicMemory.storeEpisode(<span class="string">&quot;session2&quot;</span>, <span class="string">&quot;user1&quot;</span>, </span><br><span class="line">            List.of(<span class="keyword">new</span> <span class="title class_">Message</span>(Role.USER, <span class="string">&quot;Second message&quot;</span>)), List.of());</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// Retrieve in chronological order</span></span><br><span class="line">        List&lt;EpisodicEvent&gt; episodes = episodicMemory.retrieveRelated(</span><br><span class="line">            <span class="string">&quot;messages&quot;</span>, <span class="string">&quot;user1&quot;</span>, <span class="number">10</span></span><br><span class="line">        );</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// Verify ordering</span></span><br><span class="line">        assertThat(episodes).hasSize(<span class="number">2</span>);</span><br><span class="line">        assertThat(episodes.get(<span class="number">0</span>).getTimestamp())</span><br><span class="line">            .isBefore(episodes.get(<span class="number">1</span>).getTimestamp());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Key-Takeaways"><a href="#Key-Takeaways" class="headerlink" title="Key Takeaways"></a>Key Takeaways</h2><ol><li><p><strong>Three memory types serve different purposes</strong>: Short-term for immediate context, long-term for persistent knowledge, episodic for personal experiences.</p></li><li><p><strong>Vector databases are essential for semantic retrieval</strong>: They enable similarity-based search over unstructured memory content.</p></li><li><p><strong>The Memory Router is critical</strong>: It decides what to store, what to retrieve, and how to combine memories into prompts.</p></li><li><p><strong>Performance matters</strong>: Use caching, async storage, and compaction to keep memory operations fast and cost-effective.</p></li><li><p><strong>Test your memory system</strong>: Verify retrieval accuracy, ordering, and relevance scoring with comprehensive tests.</p></li><li><p><strong>Avoid common pitfalls</strong>: Prevent memory overload, stale data, and context bloat with proper filtering and budgeting.</p></li></ol><p>Building effective agent memory is an iterative process. Start simple with short-term and long-term memory, then add episodic capabilities as your use case demands. The goal is to create agents that feel truly personalized and context-aware, rather than stateless conversationalists starting from scratch every time.</p><p>Remember: good memory architecture is the difference between an agent that forgets and one that remembers. Choose your memory types wisely, and your agents will thank you with better performance and happier users.</p>]]>
    </content>
    <id>https://thoughtfly.github.io/devtech/2026/09/10/agent-memory-architectures-short-term-long-term-and-episodic/</id>
    <link href="https://thoughtfly.github.io/devtech/2026/09/10/agent-memory-architectures-short-term-long-term-and-episodic/"/>
    <published>2026-09-10T16:00:00.000Z</published>
    <summary>Explore how LLM agents manage memory across short-term, long-term, and episodic stores. Learn practical Java implementations for persistent, context-aware AI...</summary>
    <title>Agent Memory Architectures: Short-Term, Long-Term, and Episodic</title>
    <updated>2026-09-21T14:46:52.851Z</updated>
  </entry>
  <entry>
    <author>
      <name>DevTech Team</name>
    </author>
    <category term="Java" scheme="https://thoughtfly.github.io/devtech/categories/Java/"/>
    <category term="Java" scheme="https://thoughtfly.github.io/devtech/tags/Java/"/>
    <category term="LLM" scheme="https://thoughtfly.github.io/devtech/tags/LLM/"/>
    <category term="Prompt Engineering" scheme="https://thoughtfly.github.io/devtech/tags/Prompt-Engineering/"/>
    <category term="JSON" scheme="https://thoughtfly.github.io/devtech/tags/JSON/"/>
    <category term="Structured Output" scheme="https://thoughtfly.github.io/devtech/tags/Structured-Output/"/>
    <category term="AI Integration" scheme="https://thoughtfly.github.io/devtech/tags/AI-Integration/"/>
    <content>
      <![CDATA[<h2 id="The-Hallucination-Problem-in-Production-LLMs"><a href="#The-Hallucination-Problem-in-Production-LLMs" class="headerlink" title="The Hallucination Problem in Production LLMs"></a>The Hallucination Problem in Production LLMs</h2><p>You’ve built a beautiful chatbot. It responds eloquently, answers questions with flair, and impresses everyone in the demo. Then you put it in production, and the data pipeline breaks because the model returned a JSON object with a missing field, a string where an integer was expected, or—worst of all—no JSON at all, just a poetic preamble about the nature of artificial intelligence.</p><p>This is the reality of working with Large Language Models (LLMs) in production systems. While LLMs are incredibly capable at generating natural language, they are fundamentally stochastic text predictors. They do not natively understand data schemas, types, or structural constraints unless explicitly guided. For enterprise applications, this unpredictability is a liability.</p><p>Enter <strong>Structured Output</strong> and <strong>JSON Mode</strong>. These are not just convenience features; they are essential engineering controls that transform LLMs from creative writing assistants into reliable data processing engines. In this post, we’ll explore why structured output matters, how different frameworks implement it, and how to enforce it in your Java applications with practical, production-ready examples.</p><h2 id="Why-Structured-Output-Matters"><a href="#Why-Structured-Output-Matters" class="headerlink" title="Why Structured Output Matters"></a>Why Structured Output Matters</h2><p>Before diving into implementation, it’s crucial to understand the architectural shift that structured output enables. Without it, your integration follows this fragile pattern:</p><ol><li>Send a prompt requesting structured data.</li><li>Receive a raw string response.</li><li>Attempt to parse it with regex or a JSON parser.</li><li>Handle the inevitable parsing errors, malformed JSON, or missing fields.</li><li>Pray that the next request doesn’t break the same way.</li></ol><p>This approach is brittle. LLMs can be persuaded to output markdown, code blocks, conversational filler, or completely fabricated structures. Even when they output valid JSON, the schema might drift between requests.</p><p>Structured output changes this by shifting the responsibility of format enforcement from post-hoc parsing to the generation phase. When you define a schema and enforce it at the API level or through rigorous prompting, you get:</p><ul><li><strong>Predictable data shapes</strong>: Your downstream code can safely deserialize responses without defensive parsing layers.</li><li><strong>Type safety</strong>: Integers stay integers, booleans stay booleans, and enums stay within their defined values.</li><li><strong>Reduced latency</strong>: You avoid multiple retry loops caused by malformed responses.</li><li><strong>Better observability</strong>: When the model adheres to a schema, debugging becomes a matter of checking field values, not reverse-engineering broken output.</li></ul><h2 id="Understanding-JSON-Mode-vs-Structured-Output"><a href="#Understanding-JSON-Mode-vs-Structured-Output" class="headerlink" title="Understanding JSON Mode vs. Structured Output"></a>Understanding JSON Mode vs. Structured Output</h2><p>While often used interchangeably, there’s a technical distinction between JSON mode and structured output that matters for engineers.</p><p><strong>JSON Mode</strong> is a constraint that tells the model to output only valid JSON. It prevents conversational filler like “Here is the data you requested:” or markdown code fences like &#96;&#96;&#96;json. However, JSON mode does not guarantee that the JSON conforms to a specific schema. The model might still invent fields, omit required ones, or use incorrect types, as long as the overall structure is parseable JSON.</p><p><strong>Structured Output</strong> goes a step further. It combines schema enforcement with generation. The model is either guided by a detailed schema in the prompt or, in advanced implementations, the output is validated and constrained against a schema definition. This ensures that the output not only is valid JSON but also conforms to the exact structure your application expects.</p><p>For production systems, JSON mode is a good first step, but structured output with schema validation is the goal.</p><h2 id="Implementing-Structured-Output-in-Java"><a href="#Implementing-Structured-Output-in-Java" class="headerlink" title="Implementing Structured Output in Java"></a>Implementing Structured Output in Java</h2><p>Java developers have several options for implementing structured output, ranging from direct API usage to framework-level abstractions. Let’s explore the most common approaches.</p><h3 id="Approach-1-Direct-API-with-Schema-Driven-Prompting"><a href="#Approach-1-Direct-API-with-Schema-Driven-Prompting" class="headerlink" title="Approach 1: Direct API with Schema-Driven Prompting"></a>Approach 1: Direct API with Schema-Driven Prompting</h3><p>The most fundamental approach is to craft prompts that explicitly define the expected JSON schema. This works with any LLM API that supports JSON mode or structured output parameters.</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> com.fasterxml.jackson.databind.ObjectMapper;</span><br><span class="line"><span class="keyword">import</span> com.fasterxml.jackson.databind.annotation.JsonDeserialize;</span><br><span class="line"><span class="keyword">import</span> java.util.Map;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Define your expected output structure</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SentimentAnalysisResult</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> String text;</span><br><span class="line">    <span class="keyword">private</span> String sentiment; <span class="comment">// &quot;positive&quot;, &quot;negative&quot;, &quot;neutral&quot;</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">double</span> confidence;</span><br><span class="line">    <span class="keyword">private</span> java.util.List&lt;String&gt; entities;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Getters and setters omitted for brevity</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LLMIntegration</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">ObjectMapper</span> <span class="variable">objectMapper</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ObjectMapper</span>();</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">apiKey</span> <span class="operator">=</span> System.getenv(<span class="string">&quot;LLM_API_KEY&quot;</span>);</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">baseUrl</span> <span class="operator">=</span> <span class="string">&quot;https://api.example.com/v1/chat/completions&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> SentimentAnalysisResult <span class="title function_">analyzeSentiment</span><span class="params">(String inputText)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="comment">// Construct a prompt with explicit schema definition</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">prompt</span> <span class="operator">=</span> <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">            Analyze the sentiment of the following text.</span></span><br><span class="line"><span class="string">            Return ONLY a valid JSON object with this exact structure:</span></span><br><span class="line"><span class="string">            &#123;</span></span><br><span class="line"><span class="string">              &quot;text&quot;: &quot;&lt;original text&gt;&quot;,</span></span><br><span class="line"><span class="string">              &quot;sentiment&quot;: &quot;&lt;positive|negative|neutral&gt;&quot;,</span></span><br><span class="line"><span class="string">              &quot;confidence&quot;: &lt;0.0-1.0&gt;,</span></span><br><span class="line"><span class="string">              &quot;entities&quot;: [&lt;string&gt;, ...]</span></span><br><span class="line"><span class="string">            &#125;</span></span><br><span class="line"><span class="string">            Do not include any markdown, code fences, or explanatory text.</span></span><br><span class="line"><span class="string">            </span></span><br><span class="line"><span class="string">            Text: %s</span></span><br><span class="line"><span class="string">            &quot;&quot;&quot;</span>.formatted(inputText);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Build the API request with JSON mode enabled</span></span><br><span class="line">        Map&lt;String, Object&gt; requestBody = Map.of(</span><br><span class="line">            <span class="string">&quot;model&quot;</span>, <span class="string">&quot;gpt-4o-mini&quot;</span>,</span><br><span class="line">            <span class="string">&quot;messages&quot;</span>, List.of(</span><br><span class="line">                Map.of(<span class="string">&quot;role&quot;</span>, <span class="string">&quot;user&quot;</span>, <span class="string">&quot;content&quot;</span>, prompt)</span><br><span class="line">            ),</span><br><span class="line">            <span class="string">&quot;response_format&quot;</span>, Map.of(<span class="string">&quot;type&quot;</span>, <span class="string">&quot;json_object&quot;</span>)</span><br><span class="line">        );</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Make the API call (simplified for illustration)</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">response</span> <span class="operator">=</span> callLLMApi(baseUrl, apiKey, requestBody);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// Parse the response</span></span><br><span class="line">        <span class="keyword">return</span> objectMapper.readValue(response, SentimentAnalysisResult.class);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>While this approach works, it has limitations. The schema is embedded in the prompt text, which means:</p><ol><li><strong>No compile-time safety</strong>: If you change the Java class, you must also update the prompt string.</li><li><strong>Prompt drift</strong>: The model might ignore parts of the schema instruction, especially with complex structures.</li><li><strong>Maintenance burden</strong>: As your application grows, managing schema definitions in prompts becomes unwieldy.</li></ol><h3 id="Approach-2-Using-Framework-Abstractions"><a href="#Approach-2-Using-Framework-Abstractions" class="headerlink" title="Approach 2: Using Framework Abstractions"></a>Approach 2: Using Framework Abstractions</h3><p>Modern Java frameworks like Spring AI, LangChain4j, and custom wrappers provide better abstractions for structured output. These frameworks often support schema validation and automatic deserialization.</p><p>Let’s look at a more robust implementation using a hypothetical framework pattern that many Java LLM libraries follow:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> org.springframework.ai.chat.model.ChatModel;</span><br><span class="line"><span class="keyword">import</span> org.springframework.ai.chat.prompt.Prompt;</span><br><span class="line"><span class="keyword">import</span> org.springframework.ai.chat.prompt.Message;nimport org.springframework.core.ParameterizedTypeReference;</span><br><span class="line"><span class="keyword">import</span> org.springframework.http.ResponseEntity;</span><br><span class="line"><span class="keyword">import</span> org.springframework.web.client.RestTemplate;</span><br><span class="line"><span class="keyword">import</span> java.util.List;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Define a strict schema using Jackson annotations</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ProductExtractionResult</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> List&lt;Product&gt; products;</span><br><span class="line">    <span class="keyword">private</span> String sourceDocument;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> totalProductsFound;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Product</span> &#123;</span><br><span class="line">        <span class="keyword">private</span> String name;</span><br><span class="line">        <span class="keyword">private</span> <span class="type">double</span> price;</span><br><span class="line">        <span class="keyword">private</span> String category;</span><br><span class="line">        <span class="keyword">private</span> <span class="type">boolean</span> inStock;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RobustLLMService</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> ChatModel chatModel;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> RestTemplate restTemplate;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> ProductExtractionResult <span class="title function_">extractProducts</span><span class="params">(String documentText)</span> &#123;</span><br><span class="line">        <span class="comment">// Use a system prompt to enforce schema</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">systemPrompt</span> <span class="operator">=</span> <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">            You are a data extraction assistant. </span></span><br><span class="line"><span class="string">            Extract product information from the provided text.</span></span><br><span class="line"><span class="string">            You must return ONLY valid JSON matching this schema:</span></span><br><span class="line"><span class="string">            &#123;</span></span><br><span class="line"><span class="string">              &quot;type&quot;: &quot;object&quot;,</span></span><br><span class="line"><span class="string">              &quot;properties&quot;: &#123;</span></span><br><span class="line"><span class="string">                &quot;products&quot;: &#123;</span></span><br><span class="line"><span class="string">                  &quot;type&quot;: &quot;array&quot;,</span></span><br><span class="line"><span class="string">                  &quot;items&quot;: &#123;</span></span><br><span class="line"><span class="string">                    &quot;type&quot;: &quot;object&quot;,</span></span><br><span class="line"><span class="string">                    &quot;properties&quot;: &#123;</span></span><br><span class="line"><span class="string">                      &quot;name&quot;: &#123;&quot;type&quot;: &quot;string&quot;&#125;,</span></span><br><span class="line"><span class="string">                      &quot;price&quot;: &#123;&quot;type&quot;: &quot;number&quot;&#125;,</span></span><br><span class="line"><span class="string">                      &quot;category&quot;: &#123;&quot;type&quot;: &quot;string&quot;&#125;,</span></span><br><span class="line"><span class="string">                      &quot;inStock&quot;: &#123;&quot;type&quot;: &quot;boolean&quot;&#125;</span></span><br><span class="line"><span class="string">                    &#125;,</span></span><br><span class="line"><span class="string">                    &quot;required&quot;: [&quot;name&quot;, &quot;price&quot;, &quot;category&quot;, &quot;inStock&quot;]</span></span><br><span class="line"><span class="string">                  &#125;</span></span><br><span class="line"><span class="string">                &#125;,</span></span><br><span class="line"><span class="string">                &quot;sourceDocument&quot;: &#123;&quot;type&quot;: &quot;string&quot;&#125;,</span></span><br><span class="line"><span class="string">                &quot;totalProductsFound&quot;: &#123;&quot;type&quot;: &quot;integer&quot;&#125;</span></span><br><span class="line"><span class="string">              &#125;,</span></span><br><span class="line"><span class="string">              &quot;required&quot;: [&quot;products&quot;, &quot;sourceDocument&quot;, &quot;totalProductsFound&quot;]</span></span><br><span class="line"><span class="string">            &#125;</span></span><br><span class="line"><span class="string">            Never return text outside of the JSON structure.</span></span><br><span class="line"><span class="string">            &quot;&quot;&quot;</span>;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Construct the prompt</span></span><br><span class="line">        <span class="type">Prompt</span> <span class="variable">prompt</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Prompt</span>(</span><br><span class="line">            List.of(</span><br><span class="line">                Message.systemMessage(systemPrompt),</span><br><span class="line">                Message.userMessage(documentText)</span><br><span class="line">            )</span><br><span class="line">        );</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Call the model with structured output support</span></span><br><span class="line">        <span class="comment">// Note: Actual implementation depends on the framework</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">jsonResponse</span> <span class="operator">=</span> chatModel.call(prompt).getResult().getOutput().getText();</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Validate and deserialize</span></span><br><span class="line">        <span class="keyword">return</span> validateAndParse(jsonResponse, ProductExtractionResult.class);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> &lt;T&gt; T <span class="title function_">validateAndParse</span><span class="params">(String json, Class&lt;T&gt; clazz)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// First, ensure it&#x27;s valid JSON</span></span><br><span class="line">            <span class="type">JsonNode</span> <span class="variable">node</span> <span class="operator">=</span> objectMapper.readTree(json);</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// Then deserialize to the target type</span></span><br><span class="line">            <span class="comment">// This will throw if fields are missing or types are wrong</span></span><br><span class="line">            <span class="keyword">return</span> objectMapper.treeToValue(node, clazz);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(</span><br><span class="line">                <span class="string">&quot;LLM returned invalid structured output: &quot;</span> + e.getMessage(), e</span><br><span class="line">            );</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>This approach is significantly better because:</p><ol><li><strong>Explicit schema</strong>: The JSON schema is clearly defined in the system prompt.</li><li><strong>Validation layer</strong>: We validate the output before using it.</li><li><strong>Error handling</strong>: Invalid responses throw clear exceptions rather than causing cryptic failures downstream.</li></ol><h3 id="Approach-3-Advanced-Schema-Enforcement-with-Tool-Use"><a href="#Approach-3-Advanced-Schema-Enforcement-with-Tool-Use" class="headerlink" title="Approach 3: Advanced Schema Enforcement with Tool Use"></a>Approach 3: Advanced Schema Enforcement with Tool Use</h3><p>For the highest reliability, consider using function calling or tool use. Many modern LLM APIs support structured function calling, where the model is asked to call a specific function with structured arguments. This shifts the burden of structure enforcement to the API itself, which often has better schema compliance than free-form JSON generation.</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ToolBasedExtraction</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// Define the function schema</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">EXTRACTION_FUNCTION</span> <span class="operator">=</span> <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">        &#123;</span></span><br><span class="line"><span class="string">          &quot;name&quot;: &quot;extract_products&quot;,</span></span><br><span class="line"><span class="string">          &quot;description&quot;: &quot;Extract product information from text&quot;,</span></span><br><span class="line"><span class="string">          &quot;parameters&quot;: &#123;</span></span><br><span class="line"><span class="string">            &quot;type&quot;: &quot;object&quot;,</span></span><br><span class="line"><span class="string">            &quot;properties&quot;: &#123;</span></span><br><span class="line"><span class="string">              &quot;products&quot;: &#123;</span></span><br><span class="line"><span class="string">                &quot;type&quot;: &quot;array&quot;,</span></span><br><span class="line"><span class="string">                &quot;items&quot;: &#123;</span></span><br><span class="line"><span class="string">                  &quot;type&quot;: &quot;object&quot;,</span></span><br><span class="line"><span class="string">                  &quot;properties&quot;: &#123;</span></span><br><span class="line"><span class="string">                    &quot;name&quot;: &#123;&quot;type&quot;: &quot;string&quot;, &quot;description&quot;: &quot;Product name&quot;&#125;,</span></span><br><span class="line"><span class="string">                    &quot;price&quot;: &#123;&quot;type&quot;: &quot;number&quot;, &quot;description&quot;: &quot;Product price in USD&quot;&#125;,</span></span><br><span class="line"><span class="string">                    &quot;category&quot;: &#123;&quot;type&quot;: &quot;string&quot;, &quot;description&quot;: &quot;Product category&quot;&#125;,</span></span><br><span class="line"><span class="string">                    &quot;inStock&quot;: &#123;&quot;type&quot;: &quot;boolean&quot;, &quot;description&quot;: &quot;Whether product is in stock&quot;&#125;</span></span><br><span class="line"><span class="string">                  &#125;,</span></span><br><span class="line"><span class="string">                  &quot;required&quot;: [&quot;name&quot;, &quot;price&quot;, &quot;category&quot;, &quot;inStock&quot;]</span></span><br><span class="line"><span class="string">                &#125;</span></span><br><span class="line"><span class="string">              &#125;,</span></span><br><span class="line"><span class="string">              &quot;sourceDocument&quot;: &#123;&quot;type&quot;: &quot;string&quot;&#125;</span></span><br><span class="line"><span class="string">            &#125;,</span></span><br><span class="line"><span class="string">            &quot;required&quot;: [&quot;products&quot;, &quot;sourceDocument&quot;]</span></span><br><span class="line"><span class="string">          &#125;</span></span><br><span class="line"><span class="string">        &#125;</span></span><br><span class="line"><span class="string">        &quot;&quot;&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> ProductExtractionResult <span class="title function_">extractWithTool</span><span class="params">(String documentText)</span> &#123;</span><br><span class="line">        <span class="comment">// The API handles schema enforcement when using function calling</span></span><br><span class="line">        <span class="comment">// The model must conform to the parameter schema or return an error</span></span><br><span class="line">        </span><br><span class="line">        List&lt;Message&gt; messages = List.of(</span><br><span class="line">            Message.systemMessage(<span class="string">&quot;Extract product data from the following text:&quot;</span>),</span><br><span class="line">            Message.userMessage(documentText)</span><br><span class="line">        );</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Call with function schema</span></span><br><span class="line">        <span class="type">ChatResponse</span> <span class="variable">response</span> <span class="operator">=</span> chatModel.call(</span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">Prompt</span>(messages, <span class="keyword">new</span> <span class="title class_">FunctionCallback</span>() &#123;</span><br><span class="line">                <span class="meta">@Override</span></span><br><span class="line">                <span class="keyword">public</span> String <span class="title function_">getName</span><span class="params">()</span> &#123; <span class="keyword">return</span> <span class="string">&quot;extract_products&quot;</span>; &#125;</span><br><span class="line">                <span class="meta">@Override</span></span><br><span class="line">                <span class="keyword">public</span> String <span class="title function_">getDescription</span><span class="params">()</span> &#123; <span class="keyword">return</span> <span class="string">&quot;Extract product information&quot;</span>; &#125;</span><br><span class="line">                <span class="meta">@Override</span></span><br><span class="line">                <span class="keyword">public</span> String <span class="title function_">getSchema</span><span class="params">()</span> &#123; <span class="keyword">return</span> EXTRACTION_FUNCTION; &#125;</span><br><span class="line">            &#125;)</span><br><span class="line">        );</span><br><span class="line"></span><br><span class="line">        <span class="comment">// The response is guaranteed to match the schema</span></span><br><span class="line">        <span class="keyword">return</span> parseFunctionResult(response);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Function calling is particularly powerful because:</p><ol><li><strong>API-level validation</strong>: The LLM provider validates the output against the schema before returning it.</li><li><strong>Better compliance</strong>: Models trained for function calling typically adhere more strictly to schemas than free-form JSON generation.</li><li><strong>Type safety</strong>: The structured arguments are often directly deserializable without additional parsing.</li></ol><h2 id="Common-Pitfalls-and-How-to-Avoid-Them"><a href="#Common-Pitfalls-and-How-to-Avoid-Them" class="headerlink" title="Common Pitfalls and How to Avoid Them"></a>Common Pitfalls and How to Avoid Them</h2><p>Even with structured output, you’ll encounter challenges. Here are the most common pitfalls and strategies to mitigate them.</p><h3 id="1-Schema-Drift"><a href="#1-Schema-Drift" class="headerlink" title="1. Schema Drift"></a>1. Schema Drift</h3><p>LLMs sometimes ignore parts of your schema, especially with complex nested structures. To combat this:</p><ul><li><strong>Keep schemas simple</strong>: Flatten nested objects where possible. Deeply nested schemas are harder for models to follow.</li><li><strong>Use examples</strong>: Include few-shot examples in your prompt showing correct output format.</li><li><strong>Validate aggressively</strong>: Always validate the output against your schema, even when using JSON mode.</li></ul><h3 id="2-Type-Mismatches"><a href="#2-Type-Mismatches" class="headerlink" title="2. Type Mismatches"></a>2. Type Mismatches</h3><p>Models might return a number as a string (e.g., <code>&quot;price&quot;: &quot;29.99&quot;</code> instead of <code>&quot;price&quot;: 29.99</code>). Solutions include:</p><ul><li><strong>Post-processing</strong>: Write conversion logic to handle type mismatches.</li><li><strong>Explicit type hints</strong>: In your prompt, emphasize the expected types: <code>&quot;price&quot;: &lt;number, not string&gt;</code>.</li><li><strong>Flexible deserialization</strong>: Use Jackson’s <code>@JsonDeserialize</code> with custom deserializers to handle type variations.</li></ul><h3 id="3-Missing-Fields"><a href="#3-Missing-Fields" class="headerlink" title="3. Missing Fields"></a>3. Missing Fields</h3><p>Models occasionally omit required fields. Mitigation strategies:</p><ul><li><strong>Default values</strong>: Provide default values in your schema or code.</li><li><strong>Retry logic</strong>: Implement exponential backoff retry for missing required fields.</li><li><strong>Fallback prompts</strong>: If validation fails, send a follow-up prompt asking the model to correct the output.</li></ul><h3 id="4-Hallucinated-Data"><a href="#4-Hallucinated-Data" class="headerlink" title="4. Hallucinated Data"></a>4. Hallucinated Data</h3><p>The model might invent data that isn’t in the source text. This is a content quality issue, not a structure issue, but it’s worth noting:</p><ul><li><strong>Grounding prompts</strong>: Explicitly instruct the model to only extract information present in the text.</li><li><strong>Confidence scores</strong>: Ask the model to provide confidence scores for each extracted field.</li><li><strong>Human review</strong>: For critical applications, implement human-in-the-loop validation.</li></ul><h2 id="Testing-Structured-Output"><a href="#Testing-Structured-Output" class="headerlink" title="Testing Structured Output"></a>Testing Structured Output</h2><p>Testing LLM integrations requires a different mindset than traditional unit testing. You can’t assert exact outputs, but you can assert on structure and constraints.</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> org.junit.jupiter.api.Test;</span><br><span class="line"><span class="keyword">import</span> <span class="keyword">static</span> org.junit.jupiter.api.Assertions.*;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">StructuredOutputTest</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">RobustLLMService</span> <span class="variable">service</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RobustLLMService</span>();</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">testProductExtractionStructure</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">document</span> <span class="operator">=</span> <span class="string">&quot;The new iPhone 15 costs $999 and is available in stores. &quot;</span> +</span><br><span class="line">                          <span class="string">&quot;The MacBook Pro is $1999 and currently out of stock.&quot;</span>;</span><br><span class="line"></span><br><span class="line">        <span class="type">ProductExtractionResult</span> <span class="variable">result</span> <span class="operator">=</span> service.extractProducts(document);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Assert structure, not exact values</span></span><br><span class="line">        assertNotNull(result);</span><br><span class="line">        assertNotNull(result.getProducts());</span><br><span class="line">        assertFalse(result.getProducts().isEmpty());</span><br><span class="line">        assertEquals(<span class="number">2</span>, result.getProducts().size());</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Validate each product</span></span><br><span class="line">        <span class="keyword">for</span> (ProductExtractionResult.Product product : result.getProducts()) &#123;</span><br><span class="line">            assertNotNull(product.getName());</span><br><span class="line">            assertTrue(product.getPrice() &gt; <span class="number">0</span>);</span><br><span class="line">            assertNotNull(product.getCategory());</span><br><span class="line">            <span class="comment">// Note: inStock might be inferred, so we just check it exists</span></span><br><span class="line">            assertDoesNotThrow(() -&gt; product.isInStock());</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Validate metadata</span></span><br><span class="line">        assertEquals(<span class="number">2</span>, result.getTotalProductsFound());</span><br><span class="line">        assertTrue(result.getSourceDocument().length() &gt; <span class="number">0</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">testMalformedResponseHandling</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// Simulate a malformed response scenario</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">malformedJson</span> <span class="operator">=</span> <span class="string">&quot;&#123;\&quot;products\&quot;: [], \&quot;sourceDocument\&quot;: \&quot;test\&quot;&#125;&quot;</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// This should not throw</span></span><br><span class="line">        assertDoesNotThrow(() -&gt; &#123;</span><br><span class="line">            <span class="type">ProductExtractionResult</span> <span class="variable">result</span> <span class="operator">=</span> service.validateAndParse(</span><br><span class="line">                malformedJson, </span><br><span class="line">                ProductExtractionResult.class</span><br><span class="line">            );</span><br><span class="line">            assertNotNull(result);</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Performance-Considerations"><a href="#Performance-Considerations" class="headerlink" title="Performance Considerations"></a>Performance Considerations</h2><p>Structured output can impact performance in several ways:</p><ol><li><strong>Longer prompts</strong>: Schema definitions increase prompt length, which increases token usage and latency.</li><li><strong>Validation overhead</strong>: Post-processing validation adds computational cost.</li><li><strong>Retry loops</strong>: Failed validations might trigger retries, multiplying costs.</li></ol><p>To optimize:</p><ul><li><strong>Cache schemas</strong>: Reuse schema definitions across multiple calls.</li><li><strong>Use smaller models</strong>: For structured output tasks, smaller models like GPT-4o-mini or Claude Haiku often perform comparably to larger models while being faster and cheaper.</li><li><strong>Batch processing</strong>: When possible, batch multiple extractions into a single call.</li><li><strong>Monitor token usage</strong>: Track schema-related token overhead to ensure it’s justified by reliability gains.</li></ul><h2 id="Key-Takeaways"><a href="#Key-Takeaways" class="headerlink" title="Key Takeaways"></a>Key Takeaways</h2><ol><li><p><strong>Structured output is essential for production LLMs</strong>: Unvalidated LLM responses are a recipe for fragile, broken pipelines. Schema enforcement transforms LLMs from creative assistants into reliable data processors.</p></li><li><p><strong>JSON mode is a starting point, not a solution</strong>: JSON mode prevents conversational filler but doesn’t guarantee schema compliance. Always validate output against your expected structure.</p></li><li><p><strong>Function calling offers the highest reliability</strong>: When available, use function calling or tool use APIs. They provide API-level schema enforcement that’s more reliable than prompt-based approaches.</p></li><li><p><strong>Design schemas for LLM comprehension</strong>: Keep schemas flat and simple. Deeply nested structures are harder for models to follow correctly. Use clear field descriptions and examples.</p></li><li><p><strong>Implement robust validation and error handling</strong>: Never trust LLM output blindly. Validate responses, handle type mismatches, and implement retry logic for malformed outputs.</p></li><li><p><strong>Test structure, not exact values</strong>: LLM tests should assert on schema compliance and constraints, not exact string matches. Use property-based testing and structural validation.</p></li><li><p><strong>Balance reliability with cost</strong>: Structured output adds prompt length and potential retry overhead. Choose appropriate model sizes and optimize schemas to minimize token usage while maintaining reliability.</p></li></ol><p>The future of LLM integration is structured. As models improve and APIs evolve, we’ll see tighter integration between schema definitions and generation, making structured output even more reliable and easier to implement. But even today, with careful design and validation, you can build production systems that leverage LLMs without sacrificing the reliability your users expect.</p>]]>
    </content>
    <id>https://thoughtfly.github.io/devtech/2026/09/09/structured-output-and-json-mode-for-reliable-llm-integrations/</id>
    <link href="https://thoughtfly.github.io/devtech/2026/09/09/structured-output-and-json-mode-for-reliable-llm-integrations/"/>
    <published>2026-09-09T16:00:00.000Z</published>
    <summary>Master structured output and JSON mode in LLM integrations. Learn validation techniques, schema enforcement, and practical Java examples for reliable product...</summary>
    <title>Structured Output and JSON Mode for Reliable LLM Integrations</title>
    <updated>2026-09-21T14:46:52.851Z</updated>
  </entry>
  <entry>
    <author>
      <name>DevTech Team</name>
    </author>
    <category term="AI Engineering" scheme="https://thoughtfly.github.io/devtech/categories/AI-Engineering/"/>
    <category term="Security" scheme="https://thoughtfly.github.io/devtech/categories/AI-Engineering/Security/"/>
    <category term="Java" scheme="https://thoughtfly.github.io/devtech/tags/Java/"/>
    <category term="LLM" scheme="https://thoughtfly.github.io/devtech/tags/LLM/"/>
    <category term="Python" scheme="https://thoughtfly.github.io/devtech/tags/Python/"/>
    <category term="Privacy" scheme="https://thoughtfly.github.io/devtech/tags/Privacy/"/>
    <category term="PII" scheme="https://thoughtfly.github.io/devtech/tags/PII/"/>
    <category term="Security" scheme="https://thoughtfly.github.io/devtech/tags/Security/"/>
    <content>
      <![CDATA[<h2 id="Introduction"><a href="#Introduction" class="headerlink" title="Introduction"></a>Introduction</h2><p>As organizations increasingly integrate Large Language Models (LLMs) into their workflows, a critical challenge emerges: how do we ensure that sensitive Personally Identifiable Information (PII) doesn’t leak into prompts, responses, or logs? From healthcare records to financial data, the stakes are high. A single slip can result in regulatory fines, reputational damage, and loss of customer trust.</p><p>In this post, we’ll explore practical strategies for detecting and redacting PII in LLM-powered applications, with code examples in both Python and Java.</p><h2 id="Understanding-the-PII-Landscape"><a href="#Understanding-the-PII-Landscape" class="headerlink" title="Understanding the PII Landscape"></a>Understanding the PII Landscape</h2><p>Before diving into implementation, let’s clarify what we’re protecting. PII encompasses any data that can identify an individual, including:</p><ul><li><strong>Direct identifiers</strong>: Names, SSNs, email addresses, phone numbers</li><li><strong>Indirect identifiers</strong>: IP addresses, device IDs, location data</li><li><strong>Sensitive categories</strong>: Health information, financial data, biometric records</li></ul><p>LLMs can inadvertently expose PII in several ways:</p><ul><li>Storing user input in logs</li><li>Including sensitive data in model responses</li><li>Training data contamination</li><li>Third-party API leaks</li></ul><h2 id="Key-Takeaways"><a href="#Key-Takeaways" class="headerlink" title="Key Takeaways"></a>Key Takeaways</h2><ul><li>Implement multi-layered PII detection using regex, ML models, and context-aware analysis</li><li>Use established libraries like Presidio (Python) and Presidio-Analyzer (Java) for production-ready solutions</li><li>Always redact before sending data to LLM APIs or storing in logs</li><li>Combine technical controls with organizational policies for comprehensive privacy protection</li><li>Test your redaction pipeline with realistic PII samples regularly</li></ul><p>Consider implementing a defense-in-depth approach: detect at the input layer, redact before LLM calls, and verify outputs before returning to users.</p>]]>
    </content>
    <id>https://thoughtfly.github.io/devtech/2026/09/08/pii-detection-and-redaction-in-llm-powered-applications/</id>
    <link href="https://thoughtfly.github.io/devtech/2026/09/08/pii-detection-and-redaction-in-llm-powered-applications/"/>
    <published>2026-09-08T16:00:00.000Z</published>
    <summary>Learn how to implement robust PII detection and redaction in LLM applications using open-source libraries and best practices for data privacy.</summary>
    <title>PII Detection and Redaction in LLM-Powered Applications: A Practical Engineering Guide</title>
    <updated>2026-09-21T14:46:52.851Z</updated>
  </entry>
  <entry>
    <author>
      <name>DevTech Team</name>
    </author>
    <category term="AI Engineering" scheme="https://thoughtfly.github.io/devtech/categories/AI-Engineering/"/>
    <category term="DevOps" scheme="https://thoughtfly.github.io/devtech/categories/AI-Engineering/DevOps/"/>
    <category term="Monitoring" scheme="https://thoughtfly.github.io/devtech/tags/Monitoring/"/>
    <category term="LLM" scheme="https://thoughtfly.github.io/devtech/tags/LLM/"/>
    <category term="Production" scheme="https://thoughtfly.github.io/devtech/tags/Production/"/>
    <category term="Evaluation" scheme="https://thoughtfly.github.io/devtech/tags/Evaluation/"/>
    <category term="AI Engineering" scheme="https://thoughtfly.github.io/devtech/tags/AI-Engineering/"/>
    <category term="MLOps" scheme="https://thoughtfly.github.io/devtech/tags/MLOps/"/>
    <category term="LLMOps" scheme="https://thoughtfly.github.io/devtech/tags/LLMOps/"/>
    <category term="CI/CD" scheme="https://thoughtfly.github.io/devtech/tags/CI-CD/"/>
    <content>
      <![CDATA[<h2 id="Introduction"><a href="#Introduction" class="headerlink" title="Introduction"></a>Introduction</h2><p>If you’ve ever deployed a language model to production, you know the nightmare: the model performs beautifully in your notebook, but once it hits traffic, hallucinations creep in, latency spikes, and you’re left wondering what went wrong. Traditional MLOps practices don’t map cleanly to LLM-based systems. Models aren’t static artifacts anymore—they’re dynamic, probabilistic, and constantly evolving with new prompts, fine-tunes, and retrieval strategies.</p><p>This is where LLMOps comes in. It’s not just MLOps with a new name. The operational challenges around versioning prompts, evaluating non-deterministic outputs, and monitoring semantic drift require a fundamentally different toolkit. In this post, I’ll walk through the three pillars of production LLM systems: CI&#x2F;CD pipelines, evaluation frameworks, and monitoring strategies.</p><h2 id="Why-LLMOps-Is-Different-from-Traditional-MLOps"><a href="#Why-LLMOps-Is-Different-from-Traditional-MLOps" class="headerlink" title="Why LLMOps Is Different from Traditional MLOps"></a>Why LLMOps Is Different from Traditional MLOps</h2><p>Before diving into the how, let’s understand the why. Traditional ML models have deterministic outputs given the same inputs. A fraud detection model trained on last year’s data will produce the same prediction today if fed the same features. LLMs are different. They’re non-deterministic by nature, their outputs depend on prompt context, temperature settings, and retrieval augmentation. The “model” in an LLM application is often just one component—prompts, vector stores, tool definitions, and guardrails all play roles.</p><p>This means your CI&#x2F;CD pipeline isn’t just about deploying code. It’s about deploying prompt versions, evaluating semantic quality, and ensuring that changes to any component don’t degrade the system. The evaluation metrics themselves shift from accuracy and F1 scores to human-aligned quality measures like faithfulness, relevance, and coherence.</p><h2 id="Building-CI-CD-Pipelines-for-LLM-Applications"><a href="#Building-CI-CD-Pipelines-for-LLM-Applications" class="headerlink" title="Building CI&#x2F;CD Pipelines for LLM Applications"></a>Building CI&#x2F;CD Pipelines for LLM Applications</h2><h3 id="The-Pipeline-Architecture"><a href="#The-Pipeline-Architecture" class="headerlink" title="The Pipeline Architecture"></a>The Pipeline Architecture</h3><p>A production LLM pipeline needs to handle several distinct artifacts: model weights, prompt templates, retrieval configurations, and application code. Let me show you a practical pipeline structure using GitHub Actions.</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">name:</span> <span class="string">LLMOps</span> <span class="string">Pipeline</span></span><br><span class="line"></span><br><span class="line"><span class="attr">on:</span></span><br><span class="line">  <span class="attr">push:</span></span><br><span class="line">    <span class="attr">branches:</span> [<span class="string">main</span>]</span><br><span class="line">  <span class="attr">pull_request:</span></span><br><span class="line">    <span class="attr">branches:</span> [<span class="string">main</span>]</span><br><span class="line"></span><br><span class="line"><span class="attr">jobs:</span></span><br><span class="line">  <span class="attr">evaluate:</span></span><br><span class="line">    <span class="attr">runs-on:</span> <span class="string">ubuntu-latest</span></span><br><span class="line">    <span class="attr">steps:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">uses:</span> <span class="string">actions/checkout@v4</span></span><br><span class="line">      </span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Setup</span> <span class="string">Python</span></span><br><span class="line">        <span class="attr">uses:</span> <span class="string">actions/setup-python@v5</span></span><br><span class="line">        <span class="attr">with:</span></span><br><span class="line">          <span class="attr">python-version:</span> <span class="string">&#x27;3.11&#x27;</span></span><br><span class="line">      </span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Install</span> <span class="string">dependencies</span></span><br><span class="line">        <span class="attr">run:</span> <span class="string">pip</span> <span class="string">install</span> <span class="string">-r</span> <span class="string">requirements.txt</span></span><br><span class="line">      </span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Run</span> <span class="string">evaluation</span> <span class="string">suite</span></span><br><span class="line">        <span class="attr">env:</span></span><br><span class="line">          <span class="attr">OPENAI_API_KEY:</span> <span class="string">$&#123;&#123;</span> <span class="string">secrets.OPENAI_API_KEY</span> <span class="string">&#125;&#125;</span></span><br><span class="line">          <span class="attr">HF_TOKEN:</span> <span class="string">$&#123;&#123;</span> <span class="string">secrets.HF_TOKEN</span> <span class="string">&#125;&#125;</span></span><br><span class="line">        <span class="attr">run:</span> <span class="string">python</span> <span class="string">scripts/evaluate.py</span></span><br><span class="line">      </span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Check</span> <span class="string">evaluation</span> <span class="string">thresholds</span></span><br><span class="line">        <span class="attr">run:</span> <span class="string">python</span> <span class="string">scripts/check_thresholds.py</span></span><br><span class="line">        <span class="attr">env:</span></span><br><span class="line">          <span class="attr">MIN_FAITHFULNESS:</span> <span class="number">0.85</span></span><br><span class="line">          <span class="attr">MIN_RELEVANCE:</span> <span class="number">0.80</span></span><br><span class="line">      </span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Upload</span> <span class="string">evaluation</span> <span class="string">report</span></span><br><span class="line">        <span class="attr">uses:</span> <span class="string">actions/upload-artifact@v4</span></span><br><span class="line">        <span class="attr">with:</span></span><br><span class="line">          <span class="attr">name:</span> <span class="string">eval-report</span></span><br><span class="line">          <span class="attr">path:</span> <span class="string">outputs/evaluation_report.json</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">deploy:</span></span><br><span class="line">    <span class="attr">needs:</span> <span class="string">evaluate</span></span><br><span class="line">    <span class="attr">runs-on:</span> <span class="string">ubuntu-latest</span></span><br><span class="line">    <span class="attr">if:</span> <span class="string">github.ref</span> <span class="string">==</span> <span class="string">&#x27;refs/heads/main&#x27;</span></span><br><span class="line">    <span class="attr">steps:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">uses:</span> <span class="string">actions/checkout@v4</span></span><br><span class="line">      </span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Deploy</span> <span class="string">to</span> <span class="string">production</span></span><br><span class="line">        <span class="attr">run:</span> <span class="string">./scripts/deploy.sh</span></span><br><span class="line">        <span class="attr">env:</span></span><br><span class="line">          <span class="attr">KUBECONFIG:</span> <span class="string">$&#123;&#123;</span> <span class="string">secrets.KUBECONFIG</span> <span class="string">&#125;&#125;</span></span><br><span class="line">          <span class="attr">MODEL_VERSION:</span> <span class="string">$&#123;&#123;</span> <span class="string">github.sha</span> <span class="string">&#125;&#125;</span></span><br></pre></td></tr></table></figure><h3 id="Prompt-Versioning-and-Management"><a href="#Prompt-Versioning-and-Management" class="headerlink" title="Prompt Versioning and Management"></a>Prompt Versioning and Management</h3><p>One of the most critical aspects of LLM CI&#x2F;CD is prompt versioning. Unlike code, prompts are often edited directly in production without proper tracking. Implement a prompt registry that treats prompts as first-class artifacts.</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># prompts/registry.py</span></span><br><span class="line"><span class="keyword">import</span> json</span><br><span class="line"><span class="keyword">from</span> pathlib <span class="keyword">import</span> Path</span><br><span class="line"><span class="keyword">from</span> datetime <span class="keyword">import</span> datetime</span><br><span class="line"><span class="keyword">from</span> dataclasses <span class="keyword">import</span> dataclass</span><br><span class="line"><span class="keyword">from</span> typing <span class="keyword">import</span> <span class="type">Optional</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@dataclass</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">PromptVersion</span>:</span><br><span class="line">    <span class="built_in">id</span>: <span class="built_in">str</span></span><br><span class="line">    template: <span class="built_in">str</span></span><br><span class="line">    parameters: <span class="built_in">dict</span></span><br><span class="line">    version: <span class="built_in">int</span></span><br><span class="line">    created_at: datetime</span><br><span class="line">    author: <span class="built_in">str</span></span><br><span class="line">    eval_score: <span class="type">Optional</span>[<span class="built_in">float</span>] = <span class="literal">None</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">PromptRegistry</span>:</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, registry_path: Path = Path(<span class="params"><span class="string">&quot;prompts/registry.json&quot;</span></span>)</span>):</span><br><span class="line">        <span class="variable language_">self</span>.registry_path = registry_path</span><br><span class="line">        <span class="variable language_">self</span>.prompts: <span class="built_in">dict</span>[<span class="built_in">str</span>, <span class="built_in">list</span>[PromptVersion]] = &#123;&#125;</span><br><span class="line">        <span class="variable language_">self</span>._load()</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">register</span>(<span class="params"></span></span><br><span class="line"><span class="params">        self,</span></span><br><span class="line"><span class="params">        name: <span class="built_in">str</span>,</span></span><br><span class="line"><span class="params">        template: <span class="built_in">str</span>,</span></span><br><span class="line"><span class="params">        parameters: <span class="built_in">dict</span>,</span></span><br><span class="line"><span class="params">        author: <span class="built_in">str</span></span></span><br><span class="line"><span class="params">    </span>) -&gt; PromptVersion:</span><br><span class="line">        <span class="keyword">if</span> name <span class="keyword">not</span> <span class="keyword">in</span> <span class="variable language_">self</span>.prompts:</span><br><span class="line">            <span class="variable language_">self</span>.prompts[name] = []</span><br><span class="line">        </span><br><span class="line">        current_versions = <span class="variable language_">self</span>.prompts[name]</span><br><span class="line">        next_version = <span class="built_in">len</span>(current_versions) + <span class="number">1</span></span><br><span class="line">        </span><br><span class="line">        version = PromptVersion(</span><br><span class="line">            <span class="built_in">id</span>=<span class="string">f&quot;<span class="subst">&#123;name&#125;</span>-v<span class="subst">&#123;next_version&#125;</span>&quot;</span>,</span><br><span class="line">            template=template,</span><br><span class="line">            parameters=parameters,</span><br><span class="line">            version=next_version,</span><br><span class="line">            created_at=datetime.utcnow(),</span><br><span class="line">            author=author</span><br><span class="line">        )</span><br><span class="line">        </span><br><span class="line">        <span class="variable language_">self</span>.prompts[name].append(version)</span><br><span class="line">        <span class="variable language_">self</span>._save()</span><br><span class="line">        <span class="keyword">return</span> version</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">get_latest</span>(<span class="params">self, name: <span class="built_in">str</span></span>) -&gt; PromptVersion:</span><br><span class="line">        versions = <span class="variable language_">self</span>.prompts.get(name, [])</span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> versions:</span><br><span class="line">            <span class="keyword">raise</span> ValueError(<span class="string">f&quot;Prompt <span class="subst">&#123;name&#125;</span> not found&quot;</span>)</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">max</span>(versions, key=<span class="keyword">lambda</span> v: v.version)</span><br></pre></td></tr></table></figure><h3 id="Automated-Prompt-Testing"><a href="#Automated-Prompt-Testing" class="headerlink" title="Automated Prompt Testing"></a>Automated Prompt Testing</h3><p>Your CI pipeline should run automated tests against every prompt change. Use a golden dataset of input-output pairs and verify that new prompt versions maintain or improve quality.</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># tests/test_prompts.py</span></span><br><span class="line"><span class="keyword">import</span> pytest</span><br><span class="line"><span class="keyword">from</span> prompts.registry <span class="keyword">import</span> PromptRegistry</span><br><span class="line"><span class="keyword">from</span> evaluation.metrics <span class="keyword">import</span> faithfulness, relevance, coherence</span><br><span class="line"></span><br><span class="line">registry = PromptRegistry()</span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.mark.parametrize(<span class="params"><span class="string">&quot;test_case&quot;</span>, [</span></span></span><br><span class="line"><span class="params"><span class="meta">    (<span class="params"><span class="string">&quot;customer-support-q1&quot;</span>, <span class="string">&quot;How do I reset my password?&quot;</span>, <span class="string">&quot;password-reset&quot;</span></span>),</span></span></span><br><span class="line"><span class="params"><span class="meta">    (<span class="params"><span class="string">&quot;customer-support-q2&quot;</span>, <span class="string">&quot;I need to cancel my subscription&quot;</span>, <span class="string">&quot;subscription-cancel&quot;</span></span>),</span></span></span><br><span class="line"><span class="params"><span class="meta">    (<span class="params"><span class="string">&quot;technical-faq-q1&quot;</span>, <span class="string">&quot;What&#x27;s the difference between REST and GraphQL?&quot;</span>, <span class="string">&quot;api-concepts&quot;</span></span>),</span></span></span><br><span class="line"><span class="params"><span class="meta">]</span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test_prompt_response_quality</span>(<span class="params">test_id, question, expected_category</span>):</span><br><span class="line">    prompt = registry.get_latest(<span class="string">&quot;customer-support&quot;</span>)</span><br><span class="line">    </span><br><span class="line">    <span class="comment"># Generate response using your LLM</span></span><br><span class="line">    response = generate_response(prompt, question)</span><br><span class="line">    </span><br><span class="line">    <span class="comment"># Evaluate against multiple dimensions</span></span><br><span class="line">    <span class="keyword">assert</span> faithfulness(response, question) &gt;= <span class="number">0.85</span></span><br><span class="line">    <span class="keyword">assert</span> relevance(response, question) &gt;= <span class="number">0.80</span></span><br><span class="line">    <span class="keyword">assert</span> response.category == expected_category</span><br></pre></td></tr></table></figure><h2 id="Evaluation-Frameworks-for-LLM-Applications"><a href="#Evaluation-Frameworks-for-LLM-Applications" class="headerlink" title="Evaluation Frameworks for LLM Applications"></a>Evaluation Frameworks for LLM Applications</h2><h3 id="The-Multi-Dimensional-Evaluation-Problem"><a href="#The-Multi-Dimensional-Evaluation-Problem" class="headerlink" title="The Multi-Dimensional Evaluation Problem"></a>The Multi-Dimensional Evaluation Problem</h3><p>Evaluating LLMs requires measuring multiple dimensions simultaneously. A response might be factually correct but poorly formatted, or creative but irrelevant. Traditional ML evaluation metrics don’t capture this complexity.</p><p>Here’s a comprehensive evaluation framework that covers the key dimensions:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># evaluation/evaluator.py</span></span><br><span class="line"><span class="keyword">from</span> typing <span class="keyword">import</span> <span class="type">Dict</span>, <span class="type">Any</span></span><br><span class="line"><span class="keyword">from</span> dataclasses <span class="keyword">import</span> dataclass</span><br><span class="line"><span class="keyword">from</span> enum <span class="keyword">import</span> Enum</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">EvalDimension</span>(<span class="title class_ inherited__">Enum</span>):</span><br><span class="line">    FAITHFULNESS = <span class="string">&quot;faithfulness&quot;</span></span><br><span class="line">    RELEVANCE = <span class="string">&quot;relevance&quot;</span></span><br><span class="line">    COHERENCE = <span class="string">&quot;coherence&quot;</span></span><br><span class="line">    SAFETY = <span class="string">&quot;safety&quot;</span></span><br><span class="line">    HELPFULNESS = <span class="string">&quot;helpfulness&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@dataclass</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">EvaluationResult</span>:</span><br><span class="line">    dimension: EvalDimension</span><br><span class="line">    score: <span class="built_in">float</span></span><br><span class="line">    rationale: <span class="built_in">str</span></span><br><span class="line">    metadata: <span class="type">Dict</span>[<span class="built_in">str</span>, <span class="type">Any</span>]</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">LLM</span> evaluator:</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, evaluator_model: <span class="built_in">str</span> = <span class="string">&quot;gpt-4o&quot;</span></span>):</span><br><span class="line">        <span class="variable language_">self</span>.evaluator_model = evaluator_model</span><br><span class="line">        <span class="variable language_">self</span>.dimension_evaluators = &#123;</span><br><span class="line">            EvalDimension.FAITHFULNESS: <span class="variable language_">self</span>._evaluate_faithfulness,</span><br><span class="line">            EvalDimension.RELEVANCE: <span class="variable language_">self</span>._evaluate_relevance,</span><br><span class="line">            EvalDimension.COHERENCE: <span class="variable language_">self</span>._evaluate_coherence,</span><br><span class="line">            EvalDimension.SAFETY: <span class="variable language_">self</span>._evaluate_safety,</span><br><span class="line">            EvalDimension.HELPFULNESS: <span class="variable language_">self</span>._evaluate_helpfulness,</span><br><span class="line">        &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">evaluate</span>(<span class="params"></span></span><br><span class="line"><span class="params">        self,</span></span><br><span class="line"><span class="params">        question: <span class="built_in">str</span>,</span></span><br><span class="line"><span class="params">        response: <span class="built_in">str</span>,</span></span><br><span class="line"><span class="params">        context: <span class="built_in">str</span> = <span class="string">&quot;&quot;</span></span></span><br><span class="line"><span class="params">    </span>) -&gt; <span class="type">Dict</span>[EvalDimension, EvaluationResult]:</span><br><span class="line">        results = &#123;&#125;</span><br><span class="line">        <span class="keyword">for</span> dimension, evaluator <span class="keyword">in</span> <span class="variable language_">self</span>.dimension_evaluators.items():</span><br><span class="line">            results[dimension] = evaluator(question, response, context)</span><br><span class="line">        <span class="keyword">return</span> results</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">_evaluate_faithfulness</span>(<span class="params"></span></span><br><span class="line"><span class="params">        self,</span></span><br><span class="line"><span class="params">        question: <span class="built_in">str</span>,</span></span><br><span class="line"><span class="params">        response: <span class="built_in">str</span>,</span></span><br><span class="line"><span class="params">        context: <span class="built_in">str</span></span></span><br><span class="line"><span class="params">    </span>) -&gt; EvaluationResult:</span><br><span class="line">        <span class="string">&quot;&quot;&quot;Check if response is grounded in the provided context.&quot;&quot;&quot;</span></span><br><span class="line">        prompt = <span class="string">f&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">        Evaluate the faithfulness of this response.</span></span><br><span class="line"><span class="string">        Question: <span class="subst">&#123;question&#125;</span></span></span><br><span class="line"><span class="string">        Context: <span class="subst">&#123;context&#125;</span></span></span><br><span class="line"><span class="string">        Response: <span class="subst">&#123;response&#125;</span></span></span><br><span class="line"><span class="string">        </span></span><br><span class="line"><span class="string">        Score from 0-1 how well the response is supported by the context.</span></span><br><span class="line"><span class="string">        Return JSON: &#123;&#123;&quot;score&quot;: float, &quot;rationale&quot;: str&#125;&#125;</span></span><br><span class="line"><span class="string">        &quot;&quot;&quot;</span></span><br><span class="line">        <span class="comment"># Implementation calls LLM for evaluation</span></span><br><span class="line">        <span class="keyword">return</span> EvaluationResult(</span><br><span class="line">            dimension=EvalDimension.FAITHFULNESS,</span><br><span class="line">            score=<span class="number">0.92</span>,</span><br><span class="line">            rationale=<span class="string">&quot;Response accurately reflects context&quot;</span>,</span><br><span class="line">            metadata=&#123;&#125;</span><br><span class="line">        )</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">_evaluate_relevance</span>(<span class="params"></span></span><br><span class="line"><span class="params">        self,</span></span><br><span class="line"><span class="params">        question: <span class="built_in">str</span>,</span></span><br><span class="line"><span class="params">        response: <span class="built_in">str</span>,</span></span><br><span class="line"><span class="params">        context: <span class="built_in">str</span></span></span><br><span class="line"><span class="params">    </span>) -&gt; EvaluationResult:</span><br><span class="line">        <span class="string">&quot;&quot;&quot;Check if response addresses the question.&quot;&quot;&quot;</span></span><br><span class="line">        <span class="comment"># Similar implementation...</span></span><br><span class="line">        <span class="keyword">return</span> EvaluationResult(</span><br><span class="line">            dimension=EvalDimension.RELEVANCE,</span><br><span class="line">            score=<span class="number">0.88</span>,</span><br><span class="line">            rationale=<span class="string">&quot;Response directly answers the question&quot;</span>,</span><br><span class="line">            metadata=&#123;&#125;</span><br><span class="line">        )</span><br><span class="line">    </span><br><span class="line">    <span class="comment"># ... other dimension evaluators</span></span><br></pre></td></tr></table></figure><h3 id="RAG-Specific-Evaluation"><a href="#RAG-Specific-Evaluation" class="headerlink" title="RAG-Specific Evaluation"></a>RAG-Specific Evaluation</h3><p>If you’re building retrieval-augmented generation systems, you need specialized evaluation for both the retrieval and generation components.</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># evaluation/rag_evaluator.py</span></span><br><span class="line"><span class="keyword">from</span> typing <span class="keyword">import</span> <span class="type">List</span>, <span class="type">Tuple</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">RAGEvaluator</span>:</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="variable language_">self</span>.retrieval_eval = RetrievalEvaluator()</span><br><span class="line">        <span class="variable language_">self</span>.generation_eval = GenerationEvaluator()</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">evaluate_rag_pipeline</span>(<span class="params"></span></span><br><span class="line"><span class="params">        self,</span></span><br><span class="line"><span class="params">        query: <span class="built_in">str</span>,</span></span><br><span class="line"><span class="params">        expected_answer: <span class="built_in">str</span>,</span></span><br><span class="line"><span class="params">        retrieved_documents: <span class="type">List</span>[<span class="built_in">str</span>],</span></span><br><span class="line"><span class="params">        generated_response: <span class="built_in">str</span></span></span><br><span class="line"><span class="params">    </span>) -&gt; <span class="type">Dict</span>[<span class="built_in">str</span>, <span class="type">Any</span>]:</span><br><span class="line">        <span class="comment"># Evaluate retrieval quality</span></span><br><span class="line">        retrieval_metrics = <span class="variable language_">self</span>.retrieval_eval.evaluate(</span><br><span class="line">            query, retrieved_documents, expected_answer</span><br><span class="line">        )</span><br><span class="line">        </span><br><span class="line">        <span class="comment"># Evaluate generation quality</span></span><br><span class="line">        generation_metrics = <span class="variable language_">self</span>.generation_eval.evaluate(</span><br><span class="line">            query, generated_response, retrieved_documents</span><br><span class="line">        )</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> &#123;</span><br><span class="line">            <span class="string">&quot;retrieval&quot;</span>: retrieval_metrics,</span><br><span class="line">            <span class="string">&quot;generation&quot;</span>: generation_metrics,</span><br><span class="line">            <span class="string">&quot;end_to_end&quot;</span>: &#123;</span><br><span class="line">                <span class="string">&quot;answer_accuracy&quot;</span>: <span class="variable language_">self</span>._calculate_accuracy(</span><br><span class="line">                    generated_response, expected_answer</span><br><span class="line">                ),</span><br><span class="line">                <span class="string">&quot;hallucination_score&quot;</span>: <span class="variable language_">self</span>._detect_hallucinations(</span><br><span class="line">                    generated_response, retrieved_documents</span><br><span class="line">                )</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">RetrievalEvaluator</span>:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;Evaluate retrieval component using standard IR metrics.&quot;&quot;&quot;</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">evaluate</span>(<span class="params"></span></span><br><span class="line"><span class="params">        self,</span></span><br><span class="line"><span class="params">        query: <span class="built_in">str</span>,</span></span><br><span class="line"><span class="params">        documents: <span class="type">List</span>[<span class="built_in">str</span>],</span></span><br><span class="line"><span class="params">        relevant_doc: <span class="built_in">str</span></span></span><br><span class="line"><span class="params">    </span>) -&gt; <span class="type">Dict</span>[<span class="built_in">str</span>, <span class="built_in">float</span>]:</span><br><span class="line">        <span class="keyword">return</span> &#123;</span><br><span class="line">            <span class="string">&quot;recall_at_k&quot;</span>: <span class="variable language_">self</span>._recall_at_k(documents, relevant_doc, k=<span class="number">5</span>),</span><br><span class="line">            <span class="string">&quot;precision_at_k&quot;</span>: <span class="variable language_">self</span>._precision_at_k(documents, relevant_doc, k=<span class="number">5</span>),</span><br><span class="line">            <span class="string">&quot;ndcg&quot;</span>: <span class="variable language_">self</span>._ndcg(documents, relevant_doc),</span><br><span class="line">            <span class="string">&quot;mrr&quot;</span>: <span class="variable language_">self</span>._mean_reciprocal_rank(documents, relevant_doc)</span><br><span class="line">        &#125;</span><br></pre></td></tr></table></figure><h3 id="Human-in-the-Loop-Evaluation"><a href="#Human-in-the-Loop-Evaluation" class="headerlink" title="Human-in-the-Loop Evaluation"></a>Human-in-the-Loop Evaluation</h3><p>Automated evaluation has limitations. For critical applications, implement human evaluation pipelines that sample responses for expert review.</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># evaluation/human_review.py</span></span><br><span class="line"><span class="keyword">import</span> uuid</span><br><span class="line"><span class="keyword">from</span> datetime <span class="keyword">import</span> datetime</span><br><span class="line"><span class="keyword">from</span> typing <span class="keyword">import</span> <span class="type">Optional</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">HumanReviewPipeline</span>:</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, review_threshold: <span class="built_in">float</span> = <span class="number">0.7</span></span>):</span><br><span class="line">        <span class="variable language_">self</span>.review_threshold = review_threshold</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">should_review</span>(<span class="params"></span></span><br><span class="line"><span class="params">        self,</span></span><br><span class="line"><span class="params">        eval_scores: <span class="type">Dict</span>[<span class="built_in">str</span>, <span class="built_in">float</span>],</span></span><br><span class="line"><span class="params">        confidence: <span class="built_in">float</span></span></span><br><span class="line"><span class="params">    </span>) -&gt; <span class="built_in">bool</span>:</span><br><span class="line">        <span class="string">&quot;&quot;&quot;Determine if human review is needed.&quot;&quot;&quot;</span></span><br><span class="line">        avg_score = <span class="built_in">sum</span>(eval_scores.values()) / <span class="built_in">len</span>(eval_scores)</span><br><span class="line">        </span><br><span class="line">        <span class="comment"># Flag for review if:</span></span><br><span class="line">        <span class="comment"># 1. Average score below threshold</span></span><br><span class="line">        <span class="comment"># 2. High confidence but low score (unexpected)</span></span><br><span class="line">        <span class="comment"># 3. Any single dimension critically low</span></span><br><span class="line">        critical_dimensions = [s &lt; <span class="number">0.5</span> <span class="keyword">for</span> s <span class="keyword">in</span> eval_scores.values()]</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> (</span><br><span class="line">            avg_score &lt; <span class="variable language_">self</span>.review_threshold <span class="keyword">or</span></span><br><span class="line">            <span class="built_in">any</span>(critical_dimensions)</span><br><span class="line">        )</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">create_review_task</span>(<span class="params"></span></span><br><span class="line"><span class="params">        self,</span></span><br><span class="line"><span class="params">        question: <span class="built_in">str</span>,</span></span><br><span class="line"><span class="params">        response: <span class="built_in">str</span>,</span></span><br><span class="line"><span class="params">        eval_scores: <span class="type">Dict</span>[<span class="built_in">str</span>, <span class="built_in">float</span>]</span></span><br><span class="line"><span class="params">    </span>) -&gt; <span class="built_in">dict</span>:</span><br><span class="line">        <span class="keyword">return</span> &#123;</span><br><span class="line">            <span class="string">&quot;task_id&quot;</span>: <span class="built_in">str</span>(uuid.uuid4()),</span><br><span class="line">            <span class="string">&quot;created_at&quot;</span>: datetime.utcnow().isoformat(),</span><br><span class="line">            <span class="string">&quot;question&quot;</span>: question,</span><br><span class="line">            <span class="string">&quot;response&quot;</span>: response,</span><br><span class="line">            <span class="string">&quot;auto_scores&quot;</span>: eval_scores,</span><br><span class="line">            <span class="string">&quot;status&quot;</span>: <span class="string">&quot;pending_review&quot;</span>,</span><br><span class="line">            <span class="string">&quot;priority&quot;</span>: <span class="variable language_">self</span>._calculate_priority(eval_scores)</span><br><span class="line">        &#125;</span><br></pre></td></tr></table></figure><h2 id="Production-Monitoring-Strategies"><a href="#Production-Monitoring-Strategies" class="headerlink" title="Production Monitoring Strategies"></a>Production Monitoring Strategies</h2><h3 id="The-Monitoring-Stack"><a href="#The-Monitoring-Stack" class="headerlink" title="The Monitoring Stack"></a>The Monitoring Stack</h3><p>Production LLM systems need monitoring at multiple layers: infrastructure, application, and model performance. Here’s a comprehensive monitoring setup using Prometheus and Grafana.</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># monitoring/prometheus.yml</span></span><br><span class="line"><span class="attr">global:</span></span><br><span class="line">  <span class="attr">scrape_interval:</span> <span class="string">15s</span></span><br><span class="line"></span><br><span class="line"><span class="attr">scrape_configs:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">job_name:</span> <span class="string">&#x27;llm-service&#x27;</span></span><br><span class="line">    <span class="attr">metrics_path:</span> <span class="string">&#x27;/metrics&#x27;</span></span><br><span class="line">    <span class="attr">static_configs:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">targets:</span> [<span class="string">&#x27;llm-service:8080&#x27;</span>]</span><br><span class="line">    <span class="attr">metric_relabel_configs:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">source_labels:</span> [<span class="string">__name__</span>]</span><br><span class="line">        <span class="attr">regex:</span> <span class="string">&#x27;llm_.*&#x27;</span></span><br><span class="line">        <span class="attr">action:</span> <span class="string">keep</span></span><br><span class="line"></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">job_name:</span> <span class="string">&#x27;llm-exporter&#x27;</span></span><br><span class="line">    <span class="attr">static_configs:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">targets:</span> [<span class="string">&#x27;llm-exporter:9100&#x27;</span>]</span><br></pre></td></tr></table></figure><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># monitoring/metrics.py</span></span><br><span class="line"><span class="keyword">from</span> prometheus_client <span class="keyword">import</span> Counter, Histogram, Gauge, generate_latest</span><br><span class="line"><span class="keyword">from</span> prometheus_client <span class="keyword">import</span> start_http_server</span><br><span class="line"><span class="keyword">import</span> time</span><br><span class="line"><span class="keyword">from</span> typing <span class="keyword">import</span> <span class="type">Dict</span>, <span class="type">Any</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">LLMMetrics</span>:</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="comment"># Request metrics</span></span><br><span class="line">        <span class="variable language_">self</span>.request_count = Counter(</span><br><span class="line">            <span class="string">&#x27;llm_request_count&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;Total LLM requests&#x27;</span>,</span><br><span class="line">            [<span class="string">&#x27;model&#x27;</span>, <span class="string">&#x27;endpoint&#x27;</span>, <span class="string">&#x27;status&#x27;</span>]</span><br><span class="line">        )</span><br><span class="line">        </span><br><span class="line">        <span class="variable language_">self</span>.request_latency = Histogram(</span><br><span class="line">            <span class="string">&#x27;llm_request_latency_seconds&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;LLM request latency&#x27;</span>,</span><br><span class="line">            [<span class="string">&#x27;model&#x27;</span>, <span class="string">&#x27;endpoint&#x27;</span>],</span><br><span class="line">            buckets=[<span class="number">0.1</span>, <span class="number">0.5</span>, <span class="number">1.0</span>, <span class="number">2.0</span>, <span class="number">5.0</span>, <span class="number">10.0</span>]</span><br><span class="line">        )</span><br><span class="line">        </span><br><span class="line">        <span class="variable language_">self</span>.token_usage = Histogram(</span><br><span class="line">            <span class="string">&#x27;llm_token_usage&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;Token usage per request&#x27;</span>,</span><br><span class="line">            [<span class="string">&#x27;model&#x27;</span>, <span class="string">&#x27;usage_type&#x27;</span>],</span><br><span class="line">            buckets=[<span class="number">100</span>, <span class="number">500</span>, <span class="number">1000</span>, <span class="number">5000</span>, <span class="number">10000</span>, <span class="number">50000</span>]</span><br><span class="line">        )</span><br><span class="line">        </span><br><span class="line">        <span class="comment"># Quality metrics</span></span><br><span class="line">        <span class="variable language_">self</span>.quality_score = Gauge(</span><br><span class="line">            <span class="string">&#x27;llm_quality_score&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;Average quality score from evaluations&#x27;</span>,</span><br><span class="line">            [<span class="string">&#x27;dimension&#x27;</span>, <span class="string">&#x27;model&#x27;</span>]</span><br><span class="line">        )</span><br><span class="line">        </span><br><span class="line">        <span class="comment"># Error metrics</span></span><br><span class="line">        <span class="variable language_">self</span>.error_count = Counter(</span><br><span class="line">            <span class="string">&#x27;llm_error_count&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;LLM errors&#x27;</span>,</span><br><span class="line">            [<span class="string">&#x27;error_type&#x27;</span>, <span class="string">&#x27;model&#x27;</span>]</span><br><span class="line">        )</span><br><span class="line">        </span><br><span class="line">        <span class="comment"># Business metrics</span></span><br><span class="line">        <span class="variable language_">self</span>.user_satisfaction = Gauge(</span><br><span class="line">            <span class="string">&#x27;llm_user_satisfaction&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;User satisfaction score (1-5)&#x27;</span>,</span><br><span class="line">            [<span class="string">&#x27;feature&#x27;</span>]</span><br><span class="line">        )</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">record_request</span>(<span class="params"></span></span><br><span class="line"><span class="params">        self,</span></span><br><span class="line"><span class="params">        model: <span class="built_in">str</span>,</span></span><br><span class="line"><span class="params">        endpoint: <span class="built_in">str</span>,</span></span><br><span class="line"><span class="params">        latency: <span class="built_in">float</span>,</span></span><br><span class="line"><span class="params">        input_tokens: <span class="built_in">int</span>,</span></span><br><span class="line"><span class="params">        output_tokens: <span class="built_in">int</span>,</span></span><br><span class="line"><span class="params">        success: <span class="built_in">bool</span></span></span><br><span class="line"><span class="params">    </span>):</span><br><span class="line">        status = <span class="string">&#x27;success&#x27;</span> <span class="keyword">if</span> success <span class="keyword">else</span> <span class="string">&#x27;error&#x27;</span></span><br><span class="line">        <span class="variable language_">self</span>.request_count.labels(model, endpoint, status).inc()</span><br><span class="line">        <span class="variable language_">self</span>.request_latency.labels(model, endpoint).observe(latency)</span><br><span class="line">        <span class="variable language_">self</span>.token_usage.labels(model, <span class="string">&#x27;input&#x27;</span>).observe(input_tokens)</span><br><span class="line">        <span class="variable language_">self</span>.token_usage.labels(model, <span class="string">&#x27;output&#x27;</span>).observe(output_tokens)</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> success:</span><br><span class="line">            <span class="variable language_">self</span>.error_count.labels(<span class="string">&#x27;request_error&#x27;</span>, model).inc()</span><br></pre></td></tr></table></figure><h3 id="Anomaly-Detection"><a href="#Anomaly-Detection" class="headerlink" title="Anomaly Detection"></a>Anomaly Detection</h3><p>LLM outputs can degrade gradually. Implement anomaly detection to catch performance drifts before they impact users.</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># monitoring/anomaly_detection.py</span></span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"><span class="keyword">from</span> collections <span class="keyword">import</span> deque</span><br><span class="line"><span class="keyword">from</span> typing <span class="keyword">import</span> <span class="type">Dict</span>, <span class="type">List</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">PerformanceAnomalyDetector</span>:</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, window_size: <span class="built_in">int</span> = <span class="number">1000</span>, threshold: <span class="built_in">float</span> = <span class="number">2.0</span></span>):</span><br><span class="line">        <span class="variable language_">self</span>.window_size = window_size</span><br><span class="line">        <span class="variable language_">self</span>.threshold = threshold</span><br><span class="line">        <span class="variable language_">self</span>.metrics: <span class="type">Dict</span>[<span class="built_in">str</span>, deque] = &#123;&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">add_metric</span>(<span class="params">self, name: <span class="built_in">str</span>, value: <span class="built_in">float</span></span>):</span><br><span class="line">        <span class="keyword">if</span> name <span class="keyword">not</span> <span class="keyword">in</span> <span class="variable language_">self</span>.metrics:</span><br><span class="line">            <span class="variable language_">self</span>.metrics[name] = deque(maxlen=<span class="variable language_">self</span>.window_size)</span><br><span class="line">        <span class="variable language_">self</span>.metrics[name].append(value)</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">detect_anomaly</span>(<span class="params">self, name: <span class="built_in">str</span></span>) -&gt; <span class="type">Dict</span>[<span class="built_in">str</span>, <span class="type">Any</span>]:</span><br><span class="line">        values = <span class="built_in">list</span>(<span class="variable language_">self</span>.metrics.get(name, []))</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> <span class="built_in">len</span>(values) &lt; <span class="number">100</span>:</span><br><span class="line">            <span class="keyword">return</span> &#123;<span class="string">&quot;anomaly&quot;</span>: <span class="literal">False</span>, <span class="string">&quot;reason&quot;</span>: <span class="string">&quot;insufficient_data&quot;</span>&#125;</span><br><span class="line">        </span><br><span class="line">        mean = np.mean(values)</span><br><span class="line">        std = np.std(values)</span><br><span class="line">        current = values[-<span class="number">1</span>]</span><br><span class="line">        </span><br><span class="line">        z_score = <span class="built_in">abs</span>(current - mean) / std <span class="keyword">if</span> std &gt; <span class="number">0</span> <span class="keyword">else</span> <span class="number">0</span></span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> &#123;</span><br><span class="line">            <span class="string">&quot;anomaly&quot;</span>: z_score &gt; <span class="variable language_">self</span>.threshold,</span><br><span class="line">            <span class="string">&quot;z_score&quot;</span>: z_score,</span><br><span class="line">            <span class="string">&quot;mean&quot;</span>: mean,</span><br><span class="line">            <span class="string">&quot;std&quot;</span>: std,</span><br><span class="line">            <span class="string">&quot;current&quot;</span>: current,</span><br><span class="line">            <span class="string">&quot;direction&quot;</span>: <span class="string">&quot;increase&quot;</span> <span class="keyword">if</span> current &gt; mean <span class="keyword">else</span> <span class="string">&quot;decrease&quot;</span></span><br><span class="line">        &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">get_trend</span>(<span class="params">self, name: <span class="built_in">str</span>, lookback: <span class="built_in">int</span> = <span class="number">100</span></span>) -&gt; <span class="built_in">str</span>:</span><br><span class="line">        values = <span class="built_in">list</span>(<span class="variable language_">self</span>.metrics.get(name, []))</span><br><span class="line">        <span class="keyword">if</span> <span class="built_in">len</span>(values) &lt; lookback:</span><br><span class="line">            <span class="keyword">return</span> <span class="string">&quot;insufficient_data&quot;</span></span><br><span class="line">        </span><br><span class="line">        recent = values[-lookback:]</span><br><span class="line">        older = values[:-lookback]</span><br><span class="line">        </span><br><span class="line">        recent_mean = np.mean(recent)</span><br><span class="line">        older_mean = np.mean(older)</span><br><span class="line">        </span><br><span class="line">        change = (recent_mean - older_mean) / older_mean <span class="keyword">if</span> older_mean != <span class="number">0</span> <span class="keyword">else</span> <span class="number">0</span></span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> <span class="built_in">abs</span>(change) &lt; <span class="number">0.05</span>:</span><br><span class="line">            <span class="keyword">return</span> <span class="string">&quot;stable&quot;</span></span><br><span class="line">        <span class="keyword">elif</span> change &gt; <span class="number">0</span>:</span><br><span class="line">            <span class="keyword">return</span> <span class="string">&quot;improving&quot;</span></span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            <span class="keyword">return</span> <span class="string">&quot;degrading&quot;</span></span><br></pre></td></tr></table></figure><h3 id="Real-Time-Alerting"><a href="#Real-Time-Alerting" class="headerlink" title="Real-Time Alerting"></a>Real-Time Alerting</h3><p>Set up intelligent alerting that distinguishes between transient issues and systemic problems.</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># monitoring/alertmanager.yml</span></span><br><span class="line"><span class="attr">global:</span></span><br><span class="line">  <span class="attr">resolve_timeout:</span> <span class="string">5m</span></span><br><span class="line"></span><br><span class="line"><span class="attr">route:</span></span><br><span class="line">  <span class="attr">group_by:</span> [<span class="string">&#x27;alertname&#x27;</span>, <span class="string">&#x27;model&#x27;</span>]</span><br><span class="line">  <span class="attr">group_wait:</span> <span class="string">30s</span></span><br><span class="line">  <span class="attr">group_interval:</span> <span class="string">5m</span></span><br><span class="line">  <span class="attr">repeat_interval:</span> <span class="string">4h</span></span><br><span class="line">  <span class="attr">receiver:</span> <span class="string">&#x27;slack-notifications&#x27;</span></span><br><span class="line">  <span class="attr">routes:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">match:</span></span><br><span class="line">        <span class="attr">severity:</span> <span class="string">&#x27;critical&#x27;</span></span><br><span class="line">      <span class="attr">receiver:</span> <span class="string">&#x27;pagerduty&#x27;</span></span><br><span class="line">      <span class="attr">repeat_interval:</span> <span class="string">1h</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">match:</span></span><br><span class="line">        <span class="attr">severity:</span> <span class="string">&#x27;warning&#x27;</span></span><br><span class="line">      <span class="attr">receiver:</span> <span class="string">&#x27;slack-notifications&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">receivers:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">&#x27;slack-notifications&#x27;</span></span><br><span class="line">    <span class="attr">slack_configs:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">channel:</span> <span class="string">&#x27;#llm-alerts&#x27;</span></span><br><span class="line">        <span class="attr">send_resolved:</span> <span class="literal">true</span></span><br><span class="line">        <span class="attr">title:</span> <span class="string">&#x27;&#123;&#123; .CommonAnnotations.summary &#125;&#125;&#x27;</span></span><br><span class="line">        <span class="attr">text:</span> <span class="string">&#x27;&#123;&#123; .CommonAnnotations.description &#125;&#125;&#x27;</span></span><br><span class="line"></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">&#x27;pagerduty&#x27;</span></span><br><span class="line">    <span class="attr">pagerduty_configs:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">service_key:</span> <span class="string">&#x27;&#123;&#123; secrets.PAGERDUTY_KEY &#125;&#125;&#x27;</span></span><br></pre></td></tr></table></figure><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># monitoring/alerts.py</span></span><br><span class="line"><span class="keyword">from</span> typing <span class="keyword">import</span> <span class="type">List</span>, <span class="type">Dict</span></span><br><span class="line"><span class="keyword">import</span> asyncio</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">AlertManager</span>:</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, anomaly_detector: PerformanceAnomalyDetector</span>):</span><br><span class="line">        <span class="variable language_">self</span>.detector = anomaly_detector</span><br><span class="line">        <span class="variable language_">self</span>.active_alerts: <span class="type">Dict</span>[<span class="built_in">str</span>, <span class="type">Dict</span>] = &#123;&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">async</span> <span class="keyword">def</span> <span class="title function_">check_and_alert</span>(<span class="params">self, metric_name: <span class="built_in">str</span></span>):</span><br><span class="line">        anomaly = <span class="variable language_">self</span>.detector.detect_anomaly(metric_name)</span><br><span class="line">        trend = <span class="variable language_">self</span>.detector.get_trend(metric_name)</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> anomaly[<span class="string">&quot;anomaly&quot;</span>]:</span><br><span class="line">            alert_key = <span class="string">f&quot;<span class="subst">&#123;metric_name&#125;</span>_<span class="subst">&#123;anomaly[<span class="string">&#x27;direction&#x27;</span>]&#125;</span>&quot;</span></span><br><span class="line">            </span><br><span class="line">            <span class="keyword">if</span> alert_key <span class="keyword">not</span> <span class="keyword">in</span> <span class="variable language_">self</span>.active_alerts:</span><br><span class="line">                alert = &#123;</span><br><span class="line">                    <span class="string">&quot;metric&quot;</span>: metric_name,</span><br><span class="line">                    <span class="string">&quot;severity&quot;</span>: <span class="variable language_">self</span>._determine_severity(anomaly, trend),</span><br><span class="line">                    <span class="string">&quot;message&quot;</span>: <span class="variable language_">self</span>._format_message(anomaly, trend),</span><br><span class="line">                    <span class="string">&quot;timestamp&quot;</span>: asyncio.get_event_loop().time(),</span><br><span class="line">                    <span class="string">&quot;z_score&quot;</span>: anomaly[<span class="string">&quot;z_score&quot;</span>]</span><br><span class="line">                &#125;</span><br><span class="line">                </span><br><span class="line">                <span class="variable language_">self</span>.active_alerts[alert_key] = alert</span><br><span class="line">                <span class="keyword">await</span> <span class="variable language_">self</span>._send_alert(alert)</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            <span class="comment"># Clear alert if anomaly resolved</span></span><br><span class="line">            alert_key = <span class="string">f&quot;<span class="subst">&#123;metric_name&#125;</span>_<span class="subst">&#123;<span class="string">&#x27;increase&#x27;</span> <span class="keyword">if</span> trend == <span class="string">&#x27;improving&#x27;</span> <span class="keyword">else</span> <span class="string">&#x27;decrease&#x27;</span>&#125;</span>&quot;</span></span><br><span class="line">            <span class="keyword">if</span> alert_key <span class="keyword">in</span> <span class="variable language_">self</span>.active_alerts:</span><br><span class="line">                <span class="keyword">del</span> <span class="variable language_">self</span>.active_alerts[alert_key]</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">_determine_severity</span>(<span class="params">self, anomaly: <span class="type">Dict</span>, trend: <span class="built_in">str</span></span>) -&gt; <span class="built_in">str</span>:</span><br><span class="line">        z_score = anomaly[<span class="string">&quot;z_score&quot;</span>]</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> z_score &gt; <span class="number">4</span>:</span><br><span class="line">            <span class="keyword">return</span> <span class="string">&quot;critical&quot;</span></span><br><span class="line">        <span class="keyword">elif</span> z_score &gt; <span class="number">3</span>:</span><br><span class="line">            <span class="keyword">return</span> <span class="string">&quot;high&quot;</span></span><br><span class="line">        <span class="keyword">elif</span> trend == <span class="string">&quot;degrading&quot;</span> <span class="keyword">and</span> z_score &gt; <span class="number">2</span>:</span><br><span class="line">            <span class="keyword">return</span> <span class="string">&quot;warning&quot;</span></span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;info&quot;</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">_format_message</span>(<span class="params">self, anomaly: <span class="type">Dict</span>, trend: <span class="built_in">str</span></span>) -&gt; <span class="built_in">str</span>:</span><br><span class="line">        <span class="keyword">return</span> (</span><br><span class="line">            <span class="string">f&quot;Anomaly detected in <span class="subst">&#123;anomaly.get(<span class="string">&#x27;metric&#x27;</span>, <span class="string">&#x27;unknown&#x27;</span>)&#125;</span>: &quot;</span></span><br><span class="line">            <span class="string">f&quot;Z-score <span class="subst">&#123;anomaly[<span class="string">&#x27;z_score&#x27;</span>]:<span class="number">.2</span>f&#125;</span>, trend: <span class="subst">&#123;trend&#125;</span>. &quot;</span></span><br><span class="line">            <span class="string">f&quot;Current value: <span class="subst">&#123;anomaly[<span class="string">&#x27;current&#x27;</span>]:<span class="number">.4</span>f&#125;</span>, &quot;</span></span><br><span class="line">            <span class="string">f&quot;Mean: <span class="subst">&#123;anomaly[<span class="string">&#x27;mean&#x27;</span>]:<span class="number">.4</span>f&#125;</span>&quot;</span></span><br><span class="line">        )</span><br></pre></td></tr></table></figure><h2 id="Putting-It-All-Together"><a href="#Putting-It-All-Together" class="headerlink" title="Putting It All Together"></a>Putting It All Together</h2><h3 id="The-Complete-LLMOps-Workflow"><a href="#The-Complete-LLMOps-Workflow" class="headerlink" title="The Complete LLMOps Workflow"></a>The Complete LLMOps Workflow</h3><p>Here’s how all these components work together in a production environment:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">graph TD</span><br><span class="line">    A[Code/Prompt Change] --&gt; B[CI Pipeline]</span><br><span class="line">    B --&gt; C[Automated Evaluation]</span><br><span class="line">    C --&gt; D&#123;Pass Threshold?&#125;</span><br><span class="line">    D --&gt;|No| E[Feedback to Developer]</span><br><span class="line">    D --&gt;|Yes| F[Staging Deployment]</span><br><span class="line">    F --&gt; G[Shadow Testing]</span><br><span class="line">    G --&gt; H[Production Deployment]</span><br><span class="line">    H --&gt; I[Real-time Monitoring]</span><br><span class="line">    I --&gt; J&#123;Anomaly Detected?&#125;</span><br><span class="line">    J --&gt;|Yes| K[Auto-rollback]</span><br><span class="line">    J --&gt;|No| L[Continuous Monitoring]</span><br><span class="line">    K --&gt; M[Alert Engineering]</span><br><span class="line">    M --&gt; N[Incident Response]</span><br></pre></td></tr></table></figure><h3 id="Best-Practices-Summary"><a href="#Best-Practices-Summary" class="headerlink" title="Best Practices Summary"></a>Best Practices Summary</h3><ol><li><p><strong>Version everything</strong>: Prompts, models, configurations, and datasets. Never deploy without a traceable version.</p></li><li><p><strong>Evaluate continuously</strong>: Don’t just evaluate at deployment time. Run evaluations on production traffic samples to catch drift.</p></li><li><p><strong>Set meaningful thresholds</strong>: Base your evaluation thresholds on business requirements, not arbitrary numbers. A 0.85 faithfulness score might be perfect for a chatbot but unacceptable for medical advice.</p></li><li><p><strong>Monitor the full stack</strong>: Track infrastructure metrics, application performance, and model quality separately. They often have different failure modes.</p></li><li><p><strong>Implement graceful degradation</strong>: When evaluation scores drop, have fallback mechanisms—simpler models, cached responses, or human handoff.</p></li><li><p><strong>Collect feedback loops</strong>: Enable users to rate responses and feed that data back into your evaluation and training pipelines.</p></li><li><p><strong>Document your SLOs</strong>: Define Service Level Objectives for latency, availability, and quality. Monitor them explicitly.</p></li></ol><h2 id="Key-Takeaways"><a href="#Key-Takeaways" class="headerlink" title="Key Takeaways"></a>Key Takeaways</h2><ul><li><p><strong>LLMOps requires a different mindset</strong> from traditional MLOps. Prompts, retrieval configurations, and model weights are all first-class artifacts that need versioning and testing.</p></li><li><p><strong>CI&#x2F;CD for LLMs means testing more than code</strong>. Your pipeline should validate prompt changes against golden datasets, check evaluation metrics, and only deploy when quality thresholds are met.</p></li><li><p><strong>Evaluation is multi-dimensional</strong>. Faithfulness, relevance, coherence, safety, and helpfulness all matter. Use automated evaluators for speed, but incorporate human review for critical decisions.</p></li><li><p><strong>RAG systems need specialized evaluation</strong>. Separate retrieval metrics (recall, precision, NDCG) from generation metrics (faithfulness, hallucination detection).</p></li><li><p><strong>Production monitoring must catch drift</strong>. Implement anomaly detection on quality metrics, not just latency and error rates. Gradual degradation is harder to spot than sudden failures.</p></li><li><p><strong>Alerting should be intelligent</strong>. Distinguish between transient issues and systemic problems. Auto-rollback on critical anomalies, but don’t alert on every blip.</p></li><li><p><strong>The feedback loop is essential</strong>. Production data should continuously improve your evaluation datasets and models. Without this loop, your system will stagnate.</p></li></ul><p>Building production LLM systems is hard. But with proper CI&#x2F;CD, evaluation, and monitoring practices, you can ship with confidence and catch problems before they reach users. The investment in LLMOps infrastructure pays off in reliability, maintainability, and the ability to iterate quickly on your AI features.</p>]]>
    </content>
    <id>https://thoughtfly.github.io/devtech/2026/09/07/llmops-cicd-evaluation-and-monitoring-for-ai-applications/</id>
    <link href="https://thoughtfly.github.io/devtech/2026/09/07/llmops-cicd-evaluation-and-monitoring-for-ai-applications/"/>
    <published>2026-09-07T16:00:00.000Z</published>
    <summary>Learn how to implement CI/CD pipelines, evaluation frameworks, and production monitoring for large language model applications in enterprise environments.</summary>
    <title>LLMOps: CI/CD, Evaluation, and Monitoring for AI Applications</title>
    <updated>2026-09-21T14:46:52.851Z</updated>
  </entry>
  <entry>
    <author>
      <name>DevTech Team</name>
    </author>
    <category term="Java" scheme="https://thoughtfly.github.io/devtech/categories/Java/"/>
    <category term="AI/ML" scheme="https://thoughtfly.github.io/devtech/categories/Java/AI-ML/"/>
    <category term="Java" scheme="https://thoughtfly.github.io/devtech/tags/Java/"/>
    <category term="LLM" scheme="https://thoughtfly.github.io/devtech/tags/LLM/"/>
    <category term="MLOps" scheme="https://thoughtfly.github.io/devtech/tags/MLOps/"/>
    <category term="Software Engineering" scheme="https://thoughtfly.github.io/devtech/tags/Software-Engineering/"/>
    <category term="Prompt Engineering" scheme="https://thoughtfly.github.io/devtech/tags/Prompt-Engineering/"/>
    <category term="A/B Testing" scheme="https://thoughtfly.github.io/devtech/tags/A-B-Testing/"/>
    <content>
      <![CDATA[<h2 id="Introduction-The-Hidden-Complexity-of-Prompt-Engineering"><a href="#Introduction-The-Hidden-Complexity-of-Prompt-Engineering" class="headerlink" title="Introduction: The Hidden Complexity of Prompt Engineering"></a>Introduction: The Hidden Complexity of Prompt Engineering</h2><p>When you first start building with Large Language Models (LLMs), it’s easy to fall into the trap of thinking that prompt engineering is just about writing good text. You craft a prompt, test it in the playground, and if it works, you ship it. It feels like frontend development: write some code, see the result, iterate.</p><p>But as your application grows from a prototype to a production service, this mindset becomes a liability. In production, prompts are not static strings; they are dynamic, versioned code that directly impacts your business metrics. A slight tweak to a system message can change your conversion rate by 15%. A regression in a few-shot example can silently degrade your model’s accuracy. Without rigorous management, you are flying blind.</p><p>This post explores the two critical pillars of production-grade LLM engineering: <strong>Prompt Versioning</strong> and <strong>A&#x2F;B Testing</strong>. We will move beyond theory and look at concrete implementation strategies, including how to integrate these practices into a Java-based backend using modern tools like LangChain4j and OpenTelemetry. By the end, you will have a blueprint for treating prompts with the same seriousness as your application code.</p><h2 id="Why-Prompts-Need-Versioning"><a href="#Why-Prompts-Need-Versioning" class="headerlink" title="Why Prompts Need Versioning"></a>Why Prompts Need Versioning</h2><p>In traditional software development, we version our code using Git. Every change is tracked, attributed, and reversible. Prompts, however, often live in string literals or configuration files that are rarely tracked with the same rigor. This leads to several problems:</p><ol><li><strong>Reproducibility</strong>: If a prompt performs well today, can you reproduce that result next month? Without versioning, you might not know which exact version of the prompt generated a specific output.</li><li><strong>Debugging</strong>: When a user complains about a bad response, you need to know which prompt version they encountered. Was it the latest version? An old one? Did a recent deployment change the system prompt?</li><li><strong>Rollbacks</strong>: If a new prompt version causes a spike in hallucinations or a drop in user satisfaction, you need to roll back immediately. Without versioning, this is a manual, error-prone process.</li></ol><h3 id="The-Versioning-Model"><a href="#The-Versioning-Model" class="headerlink" title="The Versioning Model"></a>The Versioning Model</h3><p>A robust prompt versioning system should include:</p><ul><li><strong>Unique Identifier</strong>: Each prompt version should have a unique ID (e.g., <code>prompt-v1.2.3</code>).</li><li><strong>Content Hash</strong>: A hash of the prompt content to detect changes.</li><li><strong>Metadata</strong>: Author, date, description, and associated experiment.</li><li><strong>Status</strong>: Draft, Active, Deprecated.</li></ul><h2 id="Implementing-Prompt-Versioning-in-Java"><a href="#Implementing-Prompt-Versioning-in-Java" class="headerlink" title="Implementing Prompt Versioning in Java"></a>Implementing Prompt Versioning in Java</h2><p>Let’s look at how to implement prompt versioning in a Java application. We’ll use <strong>LangChain4j</strong>, a popular Java framework for building LLM applications, and a simple database-backed storage system.</p><h3 id="Step-1-Define-the-Prompt-Version-Entity"><a href="#Step-1-Define-the-Prompt-Version-Entity" class="headerlink" title="Step 1: Define the Prompt Version Entity"></a>Step 1: Define the Prompt Version Entity</h3><p>First, we need a data model to represent a prompt version. This entity will store the prompt content, metadata, and version information.</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> jakarta.persistence.*;</span><br><span class="line"><span class="keyword">import</span> java.time.Instant;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Entity</span></span><br><span class="line"><span class="meta">@Table(name = &quot;prompt_versions&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">PromptVersion</span> &#123;</span><br><span class="line">    <span class="meta">@Id</span></span><br><span class="line">    <span class="meta">@GeneratedValue(strategy = GenerationType.UUID)</span></span><br><span class="line">    <span class="keyword">private</span> String id;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> String name; <span class="comment">// e.g., &quot;customer-support-system-prompt&quot;</span></span><br><span class="line">    <span class="keyword">private</span> Integer majorVersion;</span><br><span class="line">    <span class="keyword">private</span> Integer minorVersion;</span><br><span class="line">    <span class="keyword">private</span> String content; <span class="comment">// The actual prompt text</span></span><br><span class="line">    <span class="keyword">private</span> String description; <span class="comment">// Why this version was created</span></span><br><span class="line">    <span class="keyword">private</span> String author; <span class="comment">// Who created it</span></span><br><span class="line">    <span class="keyword">private</span> Instant createdAt;</span><br><span class="line">    <span class="keyword">private</span> Instant updatedAt;</span><br><span class="line">    <span class="keyword">private</span> String status; <span class="comment">// DRAFT, ACTIVE, DEPRECATED</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// Getters and Setters</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getId</span><span class="params">()</span> &#123; <span class="keyword">return</span> id; &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setId</span><span class="params">(String id)</span> &#123; <span class="built_in">this</span>.id = id; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getName</span><span class="params">()</span> &#123; <span class="keyword">return</span> name; &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setName</span><span class="params">(String name)</span> &#123; <span class="built_in">this</span>.name = name; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> Integer <span class="title function_">getMajorVersion</span><span class="params">()</span> &#123; <span class="keyword">return</span> majorVersion; &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setMajorVersion</span><span class="params">(Integer majorVersion)</span> &#123; <span class="built_in">this</span>.majorVersion = majorVersion; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> Integer <span class="title function_">getMinorVersion</span><span class="params">()</span> &#123; <span class="keyword">return</span> minorVersion; &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setMinorVersion</span><span class="params">(Integer minorVersion)</span> &#123; <span class="built_in">this</span>.minorVersion = minorVersion; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getContent</span><span class="params">()</span> &#123; <span class="keyword">return</span> content; &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setContent</span><span class="params">(String content)</span> &#123; <span class="built_in">this</span>.content = content; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getDescription</span><span class="params">()</span> &#123; <span class="keyword">return</span> description; &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setDescription</span><span class="params">(String description)</span> &#123; <span class="built_in">this</span>.description = description; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getAuthor</span><span class="params">()</span> &#123; <span class="keyword">return</span> author; &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setAuthor</span><span class="params">(String author)</span> &#123; <span class="built_in">this</span>.author = author; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> Instant <span class="title function_">getCreatedAt</span><span class="params">()</span> &#123; <span class="keyword">return</span> createdAt; &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setCreatedAt</span><span class="params">(Instant createdAt)</span> &#123; <span class="built_in">this</span>.createdAt = createdAt; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> Instant <span class="title function_">getUpdatedAt</span><span class="params">()</span> &#123; <span class="keyword">return</span> updatedAt; &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setUpdatedAt</span><span class="params">(Instant updatedAt)</span> &#123; <span class="built_in">this</span>.updatedAt = updatedAt; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getStatus</span><span class="params">()</span> &#123; <span class="keyword">return</span> status; &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setStatus</span><span class="params">(String status)</span> &#123; <span class="built_in">this</span>.status = status; &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Step-2-Create-a-Prompt-Version-Service"><a href="#Step-2-Create-a-Prompt-Version-Service" class="headerlink" title="Step 2: Create a Prompt Version Service"></a>Step 2: Create a Prompt Version Service</h3><p>Next, we need a service to manage prompt versions. This service will handle creating, updating, and retrieving prompt versions. It will also ensure that only one prompt version is active at a time for a given prompt name.</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> jakarta.enterprise.context.ApplicationScoped;</span><br><span class="line"><span class="keyword">import</span> jakarta.persistence.EntityManager;</span><br><span class="line"><span class="keyword">import</span> jakarta.persistence.PersistenceContext;</span><br><span class="line"><span class="keyword">import</span> jakarta.persistence.TypedQuery;</span><br><span class="line"><span class="keyword">import</span> jakarta.transaction.Transactional;</span><br><span class="line"><span class="keyword">import</span> java.time.Instant;</span><br><span class="line"><span class="keyword">import</span> java.util.List;</span><br><span class="line"><span class="keyword">import</span> java.util.Optional;</span><br><span class="line"></span><br><span class="line"><span class="meta">@ApplicationScoped</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">PromptVersionService</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@PersistenceContext</span></span><br><span class="line">    <span class="keyword">private</span> EntityManager entityManager;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Transactional</span></span><br><span class="line">    <span class="keyword">public</span> PromptVersion <span class="title function_">createPromptVersion</span><span class="params">(PromptVersion promptVersion)</span> &#123;</span><br><span class="line">        promptVersion.setCreatedAt(Instant.now());</span><br><span class="line">        promptVersion.setUpdatedAt(Instant.now());</span><br><span class="line">        <span class="keyword">if</span> (promptVersion.getStatus() == <span class="literal">null</span>) &#123;</span><br><span class="line">            promptVersion.setStatus(<span class="string">&quot;DRAFT&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        entityManager.persist(promptVersion);</span><br><span class="line">        <span class="keyword">return</span> promptVersion;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Transactional</span></span><br><span class="line">    <span class="keyword">public</span> PromptVersion <span class="title function_">updatePromptVersion</span><span class="params">(PromptVersion promptVersion)</span> &#123;</span><br><span class="line">        <span class="type">PromptVersion</span> <span class="variable">existing</span> <span class="operator">=</span> entityManager.find(PromptVersion.class, promptVersion.getId());</span><br><span class="line">        <span class="keyword">if</span> (existing == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;Prompt version not found: &quot;</span> + promptVersion.getId());</span><br><span class="line">        &#125;</span><br><span class="line">        existing.setContent(promptVersion.getContent());</span><br><span class="line">        existing.setDescription(promptVersion.getDescription());</span><br><span class="line">        existing.setUpdatedAt(Instant.now());</span><br><span class="line">        existing.setStatus(promptVersion.getStatus());</span><br><span class="line">        <span class="keyword">return</span> existing;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> Optional&lt;PromptVersion&gt; <span class="title function_">getActivePromptVersion</span><span class="params">(String name)</span> &#123;</span><br><span class="line">        TypedQuery&lt;PromptVersion&gt; query = entityManager.createQuery(</span><br><span class="line">            <span class="string">&quot;SELECT p FROM PromptVersion p WHERE p.name = :name AND p.status = &#x27;ACTIVE&#x27; ORDER BY p.majorVersion DESC, p.minorVersion DESC&quot;</span>, </span><br><span class="line">            PromptVersion.class</span><br><span class="line">        );</span><br><span class="line">        query.setParameter(<span class="string">&quot;name&quot;</span>, name);</span><br><span class="line">        List&lt;PromptVersion&gt; results = query.getResultList();</span><br><span class="line">        <span class="keyword">return</span> results.isEmpty() ? Optional.empty() : Optional.of(results.get(<span class="number">0</span>));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> List&lt;PromptVersion&gt; <span class="title function_">getPromptVersions</span><span class="params">(String name)</span> &#123;</span><br><span class="line">        TypedQuery&lt;PromptVersion&gt; query = entityManager.createQuery(</span><br><span class="line">            <span class="string">&quot;SELECT p FROM PromptVersion p WHERE p.name = :name ORDER BY p.majorVersion DESC, p.minorVersion DESC&quot;</span>, </span><br><span class="line">            PromptVersion.class</span><br><span class="line">        );</span><br><span class="line">        query.setParameter(<span class="string">&quot;name&quot;</span>, name);</span><br><span class="line">        <span class="keyword">return</span> query.getResultList();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Transactional</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">activatePromptVersion</span><span class="params">(String id)</span> &#123;</span><br><span class="line">        <span class="comment">// Deactivate all other versions of the same prompt</span></span><br><span class="line">        TypedQuery&lt;PromptVersion&gt; query = entityManager.createQuery(</span><br><span class="line">            <span class="string">&quot;SELECT p FROM PromptVersion p WHERE p.name = (SELECT p2.name FROM PromptVersion p2 WHERE p2.id = :id) AND p.status = &#x27;ACTIVE&#x27;&quot;</span>, </span><br><span class="line">            PromptVersion.class</span><br><span class="line">        );</span><br><span class="line">        query.setParameter(<span class="string">&quot;id&quot;</span>, id);</span><br><span class="line">        List&lt;PromptVersion&gt; activeVersions = query.getResultList();</span><br><span class="line">        <span class="keyword">for</span> (PromptVersion v : activeVersions) &#123;</span><br><span class="line">            v.setStatus(<span class="string">&quot;DEPRECATED&quot;</span>);</span><br><span class="line">            v.setUpdatedAt(Instant.now());</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Activate the new version</span></span><br><span class="line">        <span class="type">PromptVersion</span> <span class="variable">newVersion</span> <span class="operator">=</span> entityManager.find(PromptVersion.class, id);</span><br><span class="line">        <span class="keyword">if</span> (newVersion != <span class="literal">null</span>) &#123;</span><br><span class="line">            newVersion.setStatus(<span class="string">&quot;ACTIVE&quot;</span>);</span><br><span class="line">            newVersion.setUpdatedAt(Instant.now());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Step-3-Integrate-with-LangChain4j"><a href="#Step-3-Integrate-with-LangChain4j" class="headerlink" title="Step 3: Integrate with LangChain4j"></a>Step 3: Integrate with LangChain4j</h3><p>Now, let’s integrate this with LangChain4j. We’ll create a custom <code>ChatLanguageModel</code> that fetches the active prompt version before sending the request to the LLM.</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> dev.langchain4j.model.chat.ChatLanguageModel;</span><br><span class="line"><span class="keyword">import</span> dev.langchain4j.model.input.Prompt;</span><br><span class="line"><span class="keyword">import</span> dev.langchain4j.model.input.PromptTemplate;</span><br><span class="line"><span class="keyword">import</span> dev.langchain4j.model.output.Response;</span><br><span class="line"><span class="keyword">import</span> dev.langchain4j.data.message.SystemMessage;</span><br><span class="line"><span class="keyword">import</span> dev.langchain4j.data.message.UserMessage;</span><br><span class="line"><span class="keyword">import</span> jakarta.enterprise.context.ApplicationScoped;</span><br><span class="line"><span class="keyword">import</span> jakarta.inject.Inject;</span><br><span class="line"><span class="keyword">import</span> java.util.List;</span><br><span class="line"><span class="keyword">import</span> java.util.Map;</span><br><span class="line"></span><br><span class="line"><span class="meta">@ApplicationScoped</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VersionedPromptChatModel</span> <span class="keyword">implements</span> <span class="title class_">ChatLanguageModel</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Inject</span></span><br><span class="line">    <span class="keyword">private</span> PromptVersionService promptVersionService;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Inject</span></span><br><span class="line">    <span class="keyword">private</span> ChatLanguageModel delegate; <span class="comment">// e.g., OpenAiChatModel</span></span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Response&lt;String&gt; <span class="title function_">generate</span><span class="params">(List&lt;dev.langchain4j.data.message.Message&gt; messages)</span> &#123;</span><br><span class="line">        <span class="comment">// Assume the first message is a SystemMessage with the prompt name</span></span><br><span class="line">        <span class="keyword">if</span> (messages.isEmpty() || !(messages.get(<span class="number">0</span>) <span class="keyword">instanceof</span> SystemMessage)) &#123;</span><br><span class="line">            <span class="keyword">return</span> delegate.generate(messages);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="type">SystemMessage</span> <span class="variable">systemMessage</span> <span class="operator">=</span> (SystemMessage) messages.get(<span class="number">0</span>);</span><br><span class="line">        <span class="type">String</span> <span class="variable">promptName</span> <span class="operator">=</span> systemMessage.text(); <span class="comment">// The prompt name is stored in the text</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">// Fetch the active prompt version</span></span><br><span class="line">        <span class="type">var</span> <span class="variable">activePrompt</span> <span class="operator">=</span> promptVersionService.getActivePromptVersion(promptName);</span><br><span class="line">        <span class="keyword">if</span> (activePrompt.isEmpty()) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">&quot;No active prompt version found for: &quot;</span> + promptName);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Replace the system message with the prompt content</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">promptContent</span> <span class="operator">=</span> activePrompt.get().getContent();</span><br><span class="line">        List&lt;dev.langchain4j.data.message.Message&gt; updatedMessages = List.of(</span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">SystemMessage</span>(promptContent),</span><br><span class="line">            messages.subList(<span class="number">1</span>, messages.size()).toArray(<span class="keyword">new</span> <span class="title class_">dev</span>.langchain4j.data.message.Message[<span class="number">0</span>])</span><br><span class="line">        );</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> delegate.generate(updatedMessages);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>This approach allows you to manage prompt versions centrally and swap them out without changing your application code. It also provides a clear audit trail of which prompt was used for each request.</p><h2 id="A-B-Testing-LLM-Features"><a href="#A-B-Testing-LLM-Features" class="headerlink" title="A&#x2F;B Testing LLM Features"></a>A&#x2F;B Testing LLM Features</h2><p>Versioning is only half the battle. Once you have multiple prompt versions, you need a way to determine which one performs best. This is where A&#x2F;B testing comes in.</p><h3 id="What-is-A-B-Testing-for-LLMs"><a href="#What-is-A-B-Testing-for-LLMs" class="headerlink" title="What is A&#x2F;B Testing for LLMs?"></a>What is A&#x2F;B Testing for LLMs?</h3><p>A&#x2F;B testing for LLMs involves serving different prompt versions to different users or segments and measuring their impact on key metrics. Unlike traditional A&#x2F;B testing, where the metric is often a click or a conversion, LLM A&#x2F;B testing can involve more complex metrics such as:</p><ul><li><strong>Token Usage</strong>: Cost efficiency.</li><li><strong>Latency</strong>: Response time.</li><li><strong>Quality Scores</strong>: Human or automated ratings of response quality.</li><li><strong>User Satisfaction</strong>: Upvotes, downvotes, or explicit feedback.</li><li><strong>Hallucination Rate</strong>: The frequency of incorrect or fabricated information.</li></ul><h3 id="Designing-an-A-B-Test"><a href="#Designing-an-A-B-Test" class="headerlink" title="Designing an A&#x2F;B Test"></a>Designing an A&#x2F;B Test</h3><p>Let’s say you want to test two versions of a customer support prompt: <code>v1</code> and <code>v2</code>. You want to see which one leads to higher user satisfaction.</p><ol><li><strong>Define the Hypothesis</strong>: <code>v2</code> will lead to higher user satisfaction because it includes more detailed examples.</li><li><strong>Select the Metric</strong>: User satisfaction score (1-5 stars).</li><li><strong>Randomize Users</strong>: Assign each user to either <code>v1</code> or <code>v2</code> randomly.</li><li><strong>Serve the Prompt</strong>: Use the assigned prompt version for all interactions.</li><li><strong>Collect Data</strong>: Log the prompt version, user ID, and satisfaction score.</li><li><strong>Analyze Results</strong>: Compare the average satisfaction scores between the two groups.</li></ol><h3 id="Implementing-A-B-Testing-in-Java"><a href="#Implementing-A-B-Testing-in-Java" class="headerlink" title="Implementing A&#x2F;B Testing in Java"></a>Implementing A&#x2F;B Testing in Java</h3><p>We can extend our <code>PromptVersionService</code> to support A&#x2F;B testing. We’ll add a <code>Experiment</code> entity to track the test and a <code>ExperimentAssignment</code> table to record which users were assigned to which version.</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> jakarta.persistence.*;</span><br><span class="line"><span class="keyword">import</span> java.time.Instant;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Entity</span></span><br><span class="line"><span class="meta">@Table(name = &quot;experiments&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Experiment</span> &#123;</span><br><span class="line">    <span class="meta">@Id</span></span><br><span class="line">    <span class="meta">@GeneratedValue(strategy = GenerationType.UUID)</span></span><br><span class="line">    <span class="keyword">private</span> String id;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> String name; <span class="comment">// e.g., &quot;customer-support-prompt-ab-test&quot;</span></span><br><span class="line">    <span class="keyword">private</span> String description;</span><br><span class="line">    <span class="keyword">private</span> Instant startDate;</span><br><span class="line">    <span class="keyword">private</span> Instant endDate;</span><br><span class="line">    <span class="keyword">private</span> String status; <span class="comment">// RUNNING, COMPLETED, CANCELLED</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// Getters and Setters</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getId</span><span class="params">()</span> &#123; <span class="keyword">return</span> id; &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setId</span><span class="params">(String id)</span> &#123; <span class="built_in">this</span>.id = id; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getName</span><span class="params">()</span> &#123; <span class="keyword">return</span> name; &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setName</span><span class="params">(String name)</span> &#123; <span class="built_in">this</span>.name = name; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getDescription</span><span class="params">()</span> &#123; <span class="keyword">return</span> description; &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setDescription</span><span class="params">(String description)</span> &#123; <span class="built_in">this</span>.description = description; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> Instant <span class="title function_">getStartDate</span><span class="params">()</span> &#123; <span class="keyword">return</span> startDate; &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setStartDate</span><span class="params">(Instant startDate)</span> &#123; <span class="built_in">this</span>.startDate = startDate; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> Instant <span class="title function_">getEndDate</span><span class="params">()</span> &#123; <span class="keyword">return</span> endDate; &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setEndDate</span><span class="params">(Instant endDate)</span> &#123; <span class="built_in">this</span>.endDate = endDate; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getStatus</span><span class="params">()</span> &#123; <span class="keyword">return</span> status; &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setStatus</span><span class="params">(String status)</span> &#123; <span class="built_in">this</span>.status = status; &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> jakarta.persistence.*;</span><br><span class="line"><span class="keyword">import</span> java.time.Instant;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Entity</span></span><br><span class="line"><span class="meta">@Table(name = &quot;experiment_assignments&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ExperimentAssignment</span> &#123;</span><br><span class="line">    <span class="meta">@Id</span></span><br><span class="line">    <span class="meta">@GeneratedValue(strategy = GenerationType.UUID)</span></span><br><span class="line">    <span class="keyword">private</span> String id;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@ManyToOne</span></span><br><span class="line">    <span class="meta">@JoinColumn(name = &quot;experiment_id&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> Experiment experiment;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> String userId; <span class="comment">// Could be anonymous if not logged in</span></span><br><span class="line">    <span class="keyword">private</span> String promptVersionId;</span><br><span class="line">    <span class="keyword">private</span> Instant assignedAt;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Getters and Setters</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getId</span><span class="params">()</span> &#123; <span class="keyword">return</span> id; &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setId</span><span class="params">(String id)</span> &#123; <span class="built_in">this</span>.id = id; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> Experiment <span class="title function_">getExperiment</span><span class="params">()</span> &#123; <span class="keyword">return</span> experiment; &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setExperiment</span><span class="params">(Experiment experiment)</span> &#123; <span class="built_in">this</span>.experiment = experiment; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getUserId</span><span class="params">()</span> &#123; <span class="keyword">return</span> userId; &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setUserId</span><span class="params">(String userId)</span> &#123; <span class="built_in">this</span>.userId = userId; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getPromptVersionId</span><span class="params">()</span> &#123; <span class="keyword">return</span> promptVersionId; &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setPromptVersionId</span><span class="params">(String promptVersionId)</span> &#123; <span class="built_in">this</span>.promptVersionId = promptVersionId; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> Instant <span class="title function_">getAssignedAt</span><span class="params">()</span> &#123; <span class="keyword">return</span> assignedAt; &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setAssignedAt</span><span class="params">(Instant assignedAt)</span> &#123; <span class="built_in">this</span>.assignedAt = assignedAt; &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="The-A-B-Testing-Service"><a href="#The-A-B-Testing-Service" class="headerlink" title="The A&#x2F;B Testing Service"></a>The A&#x2F;B Testing Service</h3><p>Now, let’s create a service to manage A&#x2F;B tests.</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> jakarta.enterprise.context.ApplicationScoped;</span><br><span class="line"><span class="keyword">import</span> jakarta.persistence.EntityManager;</span><br><span class="line"><span class="keyword">import</span> jakarta.persistence.PersistenceContext;</span><br><span class="line"><span class="keyword">import</span> jakarta.persistence.TypedQuery;</span><br><span class="line"><span class="keyword">import</span> jakarta.transaction.Transactional;</span><br><span class="line"><span class="keyword">import</span> java.time.Instant;</span><br><span class="line"><span class="keyword">import</span> java.util.List;</span><br><span class="line"><span class="keyword">import</span> java.util.Optional;</span><br><span class="line"></span><br><span class="line"><span class="meta">@ApplicationScoped</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ExperimentService</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@PersistenceContext</span></span><br><span class="line">    <span class="keyword">private</span> EntityManager entityManager;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Transactional</span></span><br><span class="line">    <span class="keyword">public</span> Experiment <span class="title function_">createExperiment</span><span class="params">(Experiment experiment)</span> &#123;</span><br><span class="line">        experiment.setStartDate(Instant.now());</span><br><span class="line">        experiment.setStatus(<span class="string">&quot;RUNNING&quot;</span>);</span><br><span class="line">        entityManager.persist(experiment);</span><br><span class="line">        <span class="keyword">return</span> experiment;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Transactional</span></span><br><span class="line">    <span class="keyword">public</span> ExperimentAssignment <span class="title function_">assignUserToExperiment</span><span class="params">(String experimentId, String userId, String promptVersionId)</span> &#123;</span><br><span class="line">        <span class="type">ExperimentAssignment</span> <span class="variable">assignment</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ExperimentAssignment</span>();</span><br><span class="line">        assignment.setExperiment(entityManager.find(Experiment.class, experimentId));</span><br><span class="line">        assignment.setUserId(userId);</span><br><span class="line">        assignment.setPromptVersionId(promptVersionId);</span><br><span class="line">        assignment.setAssignedAt(Instant.now());</span><br><span class="line">        entityManager.persist(assignment);</span><br><span class="line">        <span class="keyword">return</span> assignment;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> Optional&lt;ExperimentAssignment&gt; <span class="title function_">getUserExperimentAssignment</span><span class="params">(String experimentId, String userId)</span> &#123;</span><br><span class="line">        TypedQuery&lt;ExperimentAssignment&gt; query = entityManager.createQuery(</span><br><span class="line">            <span class="string">&quot;SELECT a FROM ExperimentAssignment a WHERE a.experiment.id = :experimentId AND a.userId = :userId&quot;</span>, </span><br><span class="line">            ExperimentAssignment.class</span><br><span class="line">        );</span><br><span class="line">        query.setParameter(<span class="string">&quot;experimentId&quot;</span>, experimentId);</span><br><span class="line">        query.setParameter(<span class="string">&quot;userId&quot;</span>, userId);</span><br><span class="line">        List&lt;ExperimentAssignment&gt; results = query.getResultList();</span><br><span class="line">        <span class="keyword">return</span> results.isEmpty() ? Optional.empty() : Optional.of(results.get(<span class="number">0</span>));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Transactional</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">completeExperiment</span><span class="params">(String experimentId)</span> &#123;</span><br><span class="line">        <span class="type">Experiment</span> <span class="variable">experiment</span> <span class="operator">=</span> entityManager.find(Experiment.class, experimentId);</span><br><span class="line">        <span class="keyword">if</span> (experiment != <span class="literal">null</span>) &#123;</span><br><span class="line">            experiment.setStatus(<span class="string">&quot;COMPLETED&quot;</span>);</span><br><span class="line">            experiment.setEndDate(Instant.now());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Integrating-A-B-Testing-with-Prompt-Versioning"><a href="#Integrating-A-B-Testing-with-Prompt-Versioning" class="headerlink" title="Integrating A&#x2F;B Testing with Prompt Versioning"></a>Integrating A&#x2F;B Testing with Prompt Versioning</h3><p>Finally, we need to integrate the A&#x2F;B testing logic with our prompt versioning. We’ll modify the <code>VersionedPromptChatModel</code> to check if the user is part of an A&#x2F;B test and serve the appropriate prompt version.</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> dev.langchain4j.model.chat.ChatLanguageModel;</span><br><span class="line"><span class="keyword">import</span> dev.langchain4j.data.message.SystemMessage;</span><br><span class="line"><span class="keyword">import</span> dev.langchain4j.data.message.UserMessage;</span><br><span class="line"><span class="keyword">import</span> jakarta.enterprise.context.ApplicationScoped;</span><br><span class="line"><span class="keyword">import</span> jakarta.inject.Inject;</span><br><span class="line"><span class="keyword">import</span> java.util.List;</span><br><span class="line"><span class="keyword">import</span> java.util.Optional;</span><br><span class="line"></span><br><span class="line"><span class="meta">@ApplicationScoped</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VersionedPromptChatModel</span> <span class="keyword">implements</span> <span class="title class_">ChatLanguageModel</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Inject</span></span><br><span class="line">    <span class="keyword">private</span> PromptVersionService promptVersionService;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Inject</span></span><br><span class="line">    <span class="keyword">private</span> ExperimentService experimentService;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Inject</span></span><br><span class="line">    <span class="keyword">private</span> ChatLanguageModel delegate; <span class="comment">// e.g., OpenAiChatModel</span></span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Response&lt;String&gt; <span class="title function_">generate</span><span class="params">(List&lt;dev.langchain4j.data.message.Message&gt; messages)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (messages.isEmpty() || !(messages.get(<span class="number">0</span>) <span class="keyword">instanceof</span> SystemMessage)) &#123;</span><br><span class="line">            <span class="keyword">return</span> delegate.generate(messages);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="type">SystemMessage</span> <span class="variable">systemMessage</span> <span class="operator">=</span> (SystemMessage) messages.get(<span class="number">0</span>);</span><br><span class="line">        <span class="type">String</span> <span class="variable">promptName</span> <span class="operator">=</span> systemMessage.text();</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Check if this prompt is part of an A/B test</span></span><br><span class="line">        <span class="comment">// For simplicity, assume we have a way to map prompt names to experiment IDs</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">experimentId</span> <span class="operator">=</span> getExperimentIdForPrompt(promptName);</span><br><span class="line">        </span><br><span class="line">        <span class="type">String</span> <span class="variable">userId</span> <span class="operator">=</span> getCurrentUserId(); <span class="comment">// Implement this based on your auth system</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">promptVersionId</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (experimentId != <span class="literal">null</span> &amp;&amp; userId != <span class="literal">null</span>) &#123;</span><br><span class="line">            Optional&lt;ExperimentAssignment&gt; assignment = experimentService.getUserExperimentAssignment(experimentId, userId);</span><br><span class="line">            <span class="keyword">if</span> (assignment.isPresent()) &#123;</span><br><span class="line">                promptVersionId = assignment.get().getPromptVersionId();</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">// Assign user to a random version</span></span><br><span class="line">                List&lt;String&gt; versions = getVersionsForExperiment(experimentId);</span><br><span class="line">                <span class="keyword">if</span> (!versions.isEmpty()) &#123;</span><br><span class="line">                    promptVersionId = versions.get((<span class="type">int</span>) (Math.random() * versions.size()));</span><br><span class="line">                    experimentService.assignUserToExperiment(experimentId, userId, promptVersionId);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Fetch the prompt version</span></span><br><span class="line">        PromptVersion promptVersion;</span><br><span class="line">        <span class="keyword">if</span> (promptVersionId != <span class="literal">null</span>) &#123;</span><br><span class="line">            promptVersion = promptVersionService.getPromptVersionById(promptVersionId);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">// Fallback to active version</span></span><br><span class="line">            <span class="type">var</span> <span class="variable">activePrompt</span> <span class="operator">=</span> promptVersionService.getActivePromptVersion(promptName);</span><br><span class="line">            <span class="keyword">if</span> (activePrompt.isEmpty()) &#123;</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">&quot;No active prompt version found for: &quot;</span> + promptName);</span><br><span class="line">            &#125;</span><br><span class="line">            promptVersion = activePrompt.get();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Replace the system message with the prompt content</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">promptContent</span> <span class="operator">=</span> promptVersion.getContent();</span><br><span class="line">        List&lt;dev.langchain4j.data.message.Message&gt; updatedMessages = List.of(</span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">SystemMessage</span>(promptContent),</span><br><span class="line">            messages.subList(<span class="number">1</span>, messages.size()).toArray(<span class="keyword">new</span> <span class="title class_">dev</span>.langchain4j.data.message.Message[<span class="number">0</span>])</span><br><span class="line">        );</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> delegate.generate(updatedMessages);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Helper methods</span></span><br><span class="line">    <span class="keyword">private</span> String <span class="title function_">getExperimentIdForPrompt</span><span class="params">(String promptName)</span> &#123;</span><br><span class="line">        <span class="comment">// Implement logic to map prompt names to experiment IDs</span></span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>; </span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> List&lt;String&gt; <span class="title function_">getVersionsForExperiment</span><span class="params">(String experimentId)</span> &#123;</span><br><span class="line">        <span class="comment">// Implement logic to get versions for an experiment</span></span><br><span class="line">        <span class="keyword">return</span> List.of();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> String <span class="title function_">getCurrentUserId</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// Implement logic to get the current user ID</span></span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Monitoring-and-Observability"><a href="#Monitoring-and-Observability" class="headerlink" title="Monitoring and Observability"></a>Monitoring and Observability</h2><p>A&#x2F;B testing and prompt versioning generate a lot of data. It’s essential to have robust monitoring and observability to track the performance of different prompt versions and experiments.</p><h3 id="Key-Metrics-to-Track"><a href="#Key-Metrics-to-Track" class="headerlink" title="Key Metrics to Track"></a>Key Metrics to Track</h3><ol><li><strong>Token Usage</strong>: Track the number of tokens consumed by each prompt version. This helps you understand the cost impact of different prompts.</li><li><strong>Latency</strong>: Measure the response time for each prompt version. Some prompts may be more complex and take longer to process.</li><li><strong>Error Rate</strong>: Track the rate of errors (e.g., timeouts, API failures) for each prompt version.</li><li><strong>Quality Scores</strong>: If you have a feedback mechanism, track the quality scores for each prompt version.</li><li><strong>User Satisfaction</strong>: Track user satisfaction scores for each prompt version.</li></ol><h3 id="Using-OpenTelemetry-for-Observability"><a href="#Using-OpenTelemetry-for-Observability" class="headerlink" title="Using OpenTelemetry for Observability"></a>Using OpenTelemetry for Observability</h3><p>OpenTelemetry is a powerful framework for collecting telemetry data (traces, metrics, logs) from your applications. You can use it to instrument your LLM requests and capture the key metrics mentioned above.</p><p>Here’s an example of how to use OpenTelemetry to trace an LLM request with LangChain4j:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> io.opentelemetry.api.OpenTelemetry;</span><br><span class="line"><span class="keyword">import</span> io.opentelemetry.api.trace.Span;</span><br><span class="line"><span class="keyword">import</span> io.opentelemetry.api.trace.Tracer;</span><br><span class="line"><span class="keyword">import</span> jakarta.enterprise.context.ApplicationScoped;</span><br><span class="line"><span class="keyword">import</span> jakarta.inject.Inject;</span><br><span class="line"><span class="keyword">import</span> java.util.List;</span><br><span class="line"></span><br><span class="line"><span class="meta">@ApplicationScoped</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TracedChatModel</span> <span class="keyword">implements</span> <span class="title class_">ChatLanguageModel</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Inject</span></span><br><span class="line">    <span class="keyword">private</span> OpenTelemetry openTelemetry;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Inject</span></span><br><span class="line">    <span class="keyword">private</span> ChatLanguageModel delegate;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Tracer</span> <span class="variable">tracer</span> <span class="operator">=</span> OpenTelemetry.getGlobalTracerManagement().getTracer(<span class="string">&quot;chat-model&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Response&lt;String&gt; <span class="title function_">generate</span><span class="params">(List&lt;dev.langchain4j.data.message.Message&gt; messages)</span> &#123;</span><br><span class="line">        <span class="type">Span</span> <span class="variable">span</span> <span class="operator">=</span> tracer.spanBuilder(<span class="string">&quot;llm.generate&quot;</span>).startSpan();</span><br><span class="line">        <span class="keyword">try</span> (<span class="type">Scope</span> <span class="variable">scope</span> <span class="operator">=</span> span.makeCurrent()) &#123;</span><br><span class="line">            <span class="comment">// Add attributes to the span</span></span><br><span class="line">            span.setAttribute(<span class="string">&quot;llm.model&quot;</span>, <span class="string">&quot;gpt-4&quot;</span>);</span><br><span class="line">            span.setAttribute(<span class="string">&quot;llm.prompt.length&quot;</span>, messages.toString().length());</span><br><span class="line"></span><br><span class="line">            Response&lt;String&gt; response = delegate.generate(messages);</span><br><span class="line"></span><br><span class="line">            <span class="comment">// Add response attributes</span></span><br><span class="line">            span.setAttribute(<span class="string">&quot;llm.response.length&quot;</span>, response != <span class="literal">null</span> &amp;&amp; response.content() != <span class="literal">null</span> ? response.content().length() : <span class="number">0</span>);</span><br><span class="line">            span.setStatus(StatusCode.OK);</span><br><span class="line"></span><br><span class="line">            <span class="keyword">return</span> response;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            span.recordException(e);</span><br><span class="line">            span.setStatus(StatusCode.ERROR);</span><br><span class="line">            <span class="keyword">throw</span> e;</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            span.end();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>This code creates a trace for each LLM request and records important attributes such as the model name, prompt length, and response length. You can then use a observability backend like Jaeger or Zipkin to visualize these traces and identify bottlenecks or issues.</p><h2 id="Best-Practices-for-Prompt-Versioning-and-A-B-Testing"><a href="#Best-Practices-for-Prompt-Versioning-and-A-B-Testing" class="headerlink" title="Best Practices for Prompt Versioning and A&#x2F;B Testing"></a>Best Practices for Prompt Versioning and A&#x2F;B Testing</h2><ol><li><strong>Treat Prompts as Code</strong>: Use version control (Git) to track changes to your prompts. Include prompts in your CI&#x2F;CD pipeline to ensure they are tested and reviewed before deployment.</li><li><strong>Automate Testing</strong>: Write automated tests for your prompts. Use tools like Promptfoo or LangSmith to evaluate prompt performance against a set of test cases.</li><li><strong>Start with Small Experiments</strong>: Begin with small A&#x2F;B tests to validate your hypotheses before rolling out changes to all users.</li><li><strong>Monitor Continuously</strong>: Set up dashboards to monitor the performance of your prompts in real-time. Use alerts to notify you of any sudden changes in metrics.</li><li><strong>Document Everything</strong>: Keep detailed records of your prompt versions, experiments, and results. This will help you understand the history of your LLM features and make informed decisions in the future.</li></ol><h2 id="Key-Takeaways"><a href="#Key-Takeaways" class="headerlink" title="Key Takeaways"></a>Key Takeaways</h2><ul><li><strong>Prompt versioning is essential</strong> for production LLM applications. It provides reproducibility, debugging capabilities, and the ability to roll back changes quickly.</li><li><strong>A&#x2F;B testing allows you to make data-driven decisions</strong> about which prompts perform best. It helps you optimize for key metrics such as user satisfaction, latency, and cost.</li><li><strong>Integrating versioning and A&#x2F;B testing with Java</strong> can be done using frameworks like LangChain4j and persistence APIs like JPA. Custom services can manage prompt versions and experiment assignments.</li><li><strong>Observability is critical</strong> for monitoring the performance of your prompts. Use tools like OpenTelemetry to trace LLM requests and capture key metrics.</li><li><strong>Best practices include treating prompts as code, automating testing, starting with small experiments, monitoring continuously, and documenting everything.</strong></li></ul><p>By adopting these practices, you can build more reliable, efficient, and user-friendly LLM features. Remember, prompt engineering is not just about writing good text; it’s about managing a complex system with rigor and discipline.</p>]]>
    </content>
    <id>https://thoughtfly.github.io/devtech/2026/09/06/prompt-versioning-and-ab-testing-for-llm-features/</id>
    <link href="https://thoughtfly.github.io/devtech/2026/09/06/prompt-versioning-and-ab-testing-for-llm-features/"/>
    <published>2026-09-06T16:00:00.000Z</published>
    <summary>Master prompt versioning and A/B testing for LLMs. Learn practical strategies to track prompt evolution, measure performance, and ship confident AI features.</summary>
    <title>Prompt Versioning and A/B Testing for LLM Features: A Production-Ready Guide</title>
    <updated>2026-09-21T14:46:52.850Z</updated>
  </entry>
  <entry>
    <author>
      <name>DevTech Team</name>
    </author>
    <category term="Java" scheme="https://thoughtfly.github.io/devtech/categories/Java/"/>
    <category term="Java" scheme="https://thoughtfly.github.io/devtech/tags/Java/"/>
    <category term="Spring Boot" scheme="https://thoughtfly.github.io/devtech/tags/Spring-Boot/"/>
    <category term="LLM" scheme="https://thoughtfly.github.io/devtech/tags/LLM/"/>
    <category term="AI" scheme="https://thoughtfly.github.io/devtech/tags/AI/"/>
    <category term="MCP" scheme="https://thoughtfly.github.io/devtech/tags/MCP/"/>
    <content>
      <![CDATA[<h2 id="Introduction"><a href="#Introduction" class="headerlink" title="Introduction"></a>Introduction</h2><p>The Model Context Protocol (MCP) has emerged as a standard way to connect AI models with external tools and data sources. If you’re a Java developer looking to integrate MCP into your applications, this hands-on guide will walk you through building both servers and clients.</p><p>MCP enables LLMs to interact with your Java services in a standardized way, making it easier to expose tools, resources, and prompts to AI applications.</p><h2 id="What-is-MCP"><a href="#What-is-MCP" class="headerlink" title="What is MCP?"></a>What is MCP?</h2><p>MCP is an open protocol that standardizes how applications provide context to LLMs. It defines a client-server architecture where:</p><ul><li><strong>Servers</strong> expose tools, resources, and prompts</li><li><strong>Clients</strong> connect to servers and request access to these capabilities</li></ul><h2 id="Setting-Up-Your-Java-Project"><a href="#Setting-Up-Your-Java-Project" class="headerlink" title="Setting Up Your Java Project"></a>Setting Up Your Java Project</h2><p>Let’s start with a Spring Boot project structure:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// pom.xml dependencies</span></span><br><span class="line">&lt;dependencies&gt;</span><br><span class="line">    &lt;dependency&gt;</span><br><span class="line">        &lt;groupId&gt;io.modelcontextprotocol&lt;/groupId&gt;</span><br><span class="line">        &lt;artifactId&gt;mcp-server-sdk&lt;/artifactId&gt;</span><br><span class="line">        &lt;version&gt;<span class="number">0.9</span><span class="number">.0</span>&lt;/version&gt;</span><br><span class="line">    &lt;/dependency&gt;</span><br><span class="line">    &lt;dependency&gt;</span><br><span class="line">        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;</span><br><span class="line">        &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;</span><br><span class="line">    &lt;/dependency&gt;</span><br><span class="line">&lt;/dependencies&gt;</span><br></pre></td></tr></table></figure><h2 id="Building-an-MCP-Server"><a href="#Building-an-MCP-Server" class="headerlink" title="Building an MCP Server"></a>Building an MCP Server</h2><p>Here’s how to create a basic MCP server in Java:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">McpServerController</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@PostMapping(&quot;/mcp/tools&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> ResponseEntity&lt;List&lt;Tool&gt;&gt; <span class="title function_">getTools</span><span class="params">()</span> &#123;</span><br><span class="line">        List&lt;Tool&gt; tools = Arrays.asList(</span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">Tool</span>(<span class="string">&quot;get_weather&quot;</span>, <span class="string">&quot;Get weather information&quot;</span>)</span><br><span class="line">        );</span><br><span class="line">        <span class="keyword">return</span> ResponseEntity.ok(tools);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@PostMapping(&quot;/mcp/call&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> ResponseEntity&lt;CallToolResult&gt; <span class="title function_">callTool</span><span class="params">(<span class="meta">@RequestBody</span> CallToolRequest request)</span> &#123;</span><br><span class="line">        <span class="comment">// Implement tool logic</span></span><br><span class="line">        <span class="keyword">return</span> ResponseEntity.ok(<span class="keyword">new</span> <span class="title class_">CallToolResult</span>(<span class="string">&quot;sunny&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Creating-an-MCP-Client"><a href="#Creating-an-MCP-Client" class="headerlink" title="Creating an MCP Client"></a>Creating an MCP Client</h2><p>The client connects to servers and invokes tools:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">McpClient</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> WebClient webClient;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">McpClient</span><span class="params">(WebClient.Builder builder)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.webClient = builder.baseUrl(<span class="string">&quot;http://localhost:8080&quot;</span>).build();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">callTool</span><span class="params">(String toolName, Map&lt;String, Object&gt; arguments)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> webClient.post()</span><br><span class="line">            .uri(<span class="string">&quot;/mcp/call&quot;</span>)</span><br><span class="line">            .bodyValue(<span class="keyword">new</span> <span class="title class_">CallToolRequest</span>(toolName, arguments))</span><br><span class="line">            .retrieve()</span><br><span class="line">            .bodyToMono(CallToolResult.class)</span><br><span class="line">            .block();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Key-Takeaways"><a href="#Key-Takeaways" class="headerlink" title="Key Takeaways"></a>Key Takeaways</h2><ul><li>MCP provides a standardized way to connect LLMs with Java services</li><li>Servers expose tools and resources; clients consume them</li><li>Spring Boot makes MCP integration straightforward</li><li>The protocol enables secure, structured communication between AI and your applications</li></ul>]]>
    </content>
    <id>https://thoughtfly.github.io/devtech/2026/09/05/hands-on-building-mcp-servers-and-clients-in-java/</id>
    <link href="https://thoughtfly.github.io/devtech/2026/09/05/hands-on-building-mcp-servers-and-clients-in-java/"/>
    <published>2026-09-05T16:00:00.000Z</published>
    <summary>Learn how to build Model Context Protocol servers and clients in Java. Step-by-step guide with code examples for integrating LLMs with external tools.</summary>
    <title>Hands-On: Building MCP Servers and Clients in Java</title>
    <updated>2026-09-21T14:46:52.850Z</updated>
  </entry>
  <entry>
    <author>
      <name>DevTech Team</name>
    </author>
    <category term="AI/ML" scheme="https://thoughtfly.github.io/devtech/categories/AI-ML/"/>
    <category term="Software Engineering" scheme="https://thoughtfly.github.io/devtech/categories/AI-ML/Software-Engineering/"/>
    <category term="RAG" scheme="https://thoughtfly.github.io/devtech/tags/RAG/"/>
    <category term="LLM" scheme="https://thoughtfly.github.io/devtech/tags/LLM/"/>
    <category term="AI Engineering" scheme="https://thoughtfly.github.io/devtech/tags/AI-Engineering/"/>
    <category term="GraphRAG" scheme="https://thoughtfly.github.io/devtech/tags/GraphRAG/"/>
    <category term="Knowledge Graphs" scheme="https://thoughtfly.github.io/devtech/tags/Knowledge-Graphs/"/>
    <category term="Natural Language Processing" scheme="https://thoughtfly.github.io/devtech/tags/Natural-Language-Processing/"/>
    <content>
      <![CDATA[<h2 id="The-Hallucination-Problem-in-Modern-LLM-Applications"><a href="#The-Hallucination-Problem-in-Modern-LLM-Applications" class="headerlink" title="The Hallucination Problem in Modern LLM Applications"></a>The Hallucination Problem in Modern LLM Applications</h2><p>As engineers, we’ve all been there. You build a beautiful Retrieval-Augmented Generation (RAG) pipeline, feed it your company’s documentation, and watch in horror as the LLM confidently invents facts that aren’t there. The classic RAG approach—chunking documents, embedding them, and retrieving similar text—works well for factual recall but struggles with complex reasoning across multiple documents.</p><p>This is where GraphRAG emerges as a transformative approach. By integrating knowledge graphs into the retrieval process, we can provide LLMs with structured, interconnected context that significantly improves answer accuracy and reduces hallucinations.</p><h2 id="What-is-GraphRAG"><a href="#What-is-GraphRAG" class="headerlink" title="What is GraphRAG?"></a>What is GraphRAG?</h2><p>GraphRAG combines the strengths of knowledge graphs with large language models. Instead of relying solely on vector similarity to retrieve text chunks, GraphRAG extracts entities and relationships from your documents to build a knowledge graph. This graph then serves as a structured index that guides both retrieval and generation.</p><p>The key insight is that knowledge graphs capture explicit relationships between entities—people, places, concepts, events—that traditional vector search misses. When an LLM queries this graph, it receives not just related text snippets but also the semantic connections between them, enabling more coherent and factually grounded responses.</p><h2 id="Why-Traditional-RAG-Falls-Short"><a href="#Why-Traditional-RAG-Falls-Short" class="headerlink" title="Why Traditional RAG Falls Short"></a>Why Traditional RAG Falls Short</h2><p>Traditional RAG systems face several fundamental limitations:</p><p><strong>Fragmented Context</strong>: When documents are chunked and embedded independently, the relationships between chunks are lost. The LLM receives isolated pieces of information without understanding how they connect.</p><p><strong>Missing Global Context</strong>: Vector search excels at finding local similarities but struggles with global questions that require synthesizing information across entire documents or knowledge bases.</p><p><strong>No Explicit Reasoning</strong>: Without structured relationships, the LLM must infer connections from text alone, leading to potential reasoning errors and hallucinations.</p><p><strong>Limited Traceability</strong>: It’s difficult to explain why a particular answer was generated or which sources supported specific claims.</p><p>GraphRAG addresses these issues by providing a structured representation of knowledge that preserves relationships and enables multi-hop reasoning.</p><h2 id="How-GraphRAG-Works-The-Architecture"><a href="#How-GraphRAG-Works-The-Architecture" class="headerlink" title="How GraphRAG Works: The Architecture"></a>How GraphRAG Works: The Architecture</h2><p>Building a GraphRAG system involves several interconnected components. Let me walk you through each stage.</p><h3 id="Entity-and-Relationship-Extraction"><a href="#Entity-and-Relationship-Extraction" class="headerlink" title="Entity and Relationship Extraction"></a>Entity and Relationship Extraction</h3><p>The first step is extracting entities and relationships from your source documents. This can be done using LLMs themselves or specialized NLP pipelines.</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Example: Using an LLM API to extract entities and relationships</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">EntityExtractor</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> KnowledgeGraph <span class="title function_">extractEntities</span><span class="params">(String document)</span> &#123;</span><br><span class="line">        <span class="type">KnowledgeGraph</span> <span class="variable">graph</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">KnowledgeGraph</span>();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// Prompt the LLM to extract entities and relationships</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">prompt</span> <span class="operator">=</span> buildExtractionPrompt(document);</span><br><span class="line">        <span class="type">LlmResponse</span> <span class="variable">response</span> <span class="operator">=</span> llmClient.generate(prompt);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// Parse the response into graph structure</span></span><br><span class="line">        List&lt;Entity&gt; entities = parseEntities(response);</span><br><span class="line">        List&lt;Relationship&gt; relationships = parseRelationships(response);</span><br><span class="line">        </span><br><span class="line">        graph.addEntities(entities);</span><br><span class="line">        graph.addRelationships(relationships);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> graph;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> String <span class="title function_">buildExtractionPrompt</span><span class="params">(String document)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">            Extract all entities and relationships from the following text.</span></span><br><span class="line"><span class="string">            Return the result as a JSON array of objects with &#x27;entity&#x27;, &#x27;type&#x27;, </span></span><br><span class="line"><span class="string">            &#x27;relationship&#x27;, and &#x27;target&#x27; fields.</span></span><br><span class="line"><span class="string">            </span></span><br><span class="line"><span class="string">            Text: %s</span></span><br><span class="line"><span class="string">            </span></span><br><span class="line"><span class="string">            JSON:</span></span><br><span class="line"><span class="string">            &quot;&quot;&quot;</span>.formatted(document);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Graph-Storage-and-Indexing"><a href="#Graph-Storage-and-Indexing" class="headerlink" title="Graph Storage and Indexing"></a>Graph Storage and Indexing</h3><p>Once extracted, the knowledge graph needs to be stored efficiently. Popular choices include graph databases like Neo4j, Neptune, or TigerGraph, or in-memory graph libraries for smaller deployments.</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Neo4j configuration for GraphRAG</span></span><br><span class="line"></span><br><span class="line"><span class="attr">dbms:</span></span><br><span class="line">  <span class="attr">memory:</span></span><br><span class="line">    <span class="attr">heap.initial_size:</span> <span class="string">4g</span></span><br><span class="line">    <span class="attr">heap.max_size:</span> <span class="string">8g</span></span><br><span class="line">    <span class="attr">pagecache.size:</span> <span class="string">2g</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="attr">dbms.security.auth_enabled:</span> <span class="literal">true</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Enable full-text indexes for entity names</span></span><br><span class="line"></span><br><span class="line"><span class="attr">db.index.fulltext.entity_names:</span></span><br><span class="line">  <span class="attr">enabled:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">node_keys_indexable:</span> [<span class="string">&quot;name&quot;</span>, <span class="string">&quot;type&quot;</span>]</span><br></pre></td></tr></table></figure><h3 id="Hybrid-Retrieval-Strategy"><a href="#Hybrid-Retrieval-Strategy" class="headerlink" title="Hybrid Retrieval Strategy"></a>Hybrid Retrieval Strategy</h3><p>GraphRAG employs a hybrid retrieval approach that combines vector search with graph traversal. This multi-pronged strategy ensures comprehensive context gathering.</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Hybrid retrieval combining vector and graph search</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">GraphRAGRetriever</span>:</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">retrieve</span>(<span class="params">self, query: <span class="built_in">str</span>, top_k: <span class="built_in">int</span> = <span class="number">10</span></span>) -&gt; <span class="type">List</span>[Context]:</span><br><span class="line">        <span class="comment"># 1. Vector search for semantic similarity</span></span><br><span class="line">        vector_results = <span class="variable language_">self</span>.vector_store.similarity_search(</span><br><span class="line">            query, k=top_k</span><br><span class="line">        )</span><br><span class="line">        </span><br><span class="line">        <span class="comment"># 2. Entity extraction from query</span></span><br><span class="line">        query_entities = <span class="variable language_">self</span>.entity_extractor.extract(query)</span><br><span class="line">        </span><br><span class="line">        <span class="comment"># 3. Graph traversal from extracted entities</span></span><br><span class="line">        graph_results = <span class="variable language_">self</span>.graph_db.traverse(</span><br><span class="line">            entities=query_entities,</span><br><span class="line">            depth=<span class="number">2</span>,</span><br><span class="line">            max_results=top_k</span><br><span class="line">        )</span><br><span class="line">        </span><br><span class="line">        <span class="comment"># 4. Merge and deduplicate results</span></span><br><span class="line">        combined = <span class="variable language_">self</span>.merge_results(vector_results, graph_results)</span><br><span class="line">        </span><br><span class="line">        <span class="comment"># 5. Rank by relevance score</span></span><br><span class="line">        ranked = <span class="variable language_">self</span>.rank_results(combined, query)</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> ranked[:top_k]</span><br></pre></td></tr></table></figure><h3 id="Context-Augmentation-for-Generation"><a href="#Context-Augmentation-for-Generation" class="headerlink" title="Context Augmentation for Generation"></a>Context Augmentation for Generation</h3><p>The retrieved context—both text chunks and graph structures—is then augmented into the LLM prompt. The key is presenting the graph information in a format the LLM can understand and reason with.</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ContextAugmenter</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> PromptContext <span class="title function_">augment</span><span class="params">(Context[] retrievedContext)</span> &#123;</span><br><span class="line">        <span class="type">PromptContext</span> <span class="variable">context</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">PromptContext</span>();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// Extract text chunks</span></span><br><span class="line">        List&lt;String&gt; textChunks = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">        Set&lt;String&gt; entities = <span class="keyword">new</span> <span class="title class_">HashSet</span>&lt;&gt;();</span><br><span class="line">        List&lt;String&gt; relationships = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> (Context item : retrievedContext) &#123;</span><br><span class="line">            textChunks.add(item.getText());</span><br><span class="line">            entities.addAll(item.getEntities());</span><br><span class="line">            relationships.addAll(item.getRelationships());</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        context.setTextChunks(textChunks);</span><br><span class="line">        context.setEntities(entities);</span><br><span class="line">        context.setRelationships(relationships);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> context;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">buildPrompt</span><span class="params">(String query, PromptContext context)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">            You are a helpful assistant with access to a knowledge graph.</span></span><br><span class="line"><span class="string">            </span></span><br><span class="line"><span class="string">            Query: %s</span></span><br><span class="line"><span class="string">            </span></span><br><span class="line"><span class="string">            Relevant entities: %s</span></span><br><span class="line"><span class="string">            </span></span><br><span class="line"><span class="string">            Key relationships:</span></span><br><span class="line"><span class="string">            %s</span></span><br><span class="line"><span class="string">            </span></span><br><span class="line"><span class="string">            Supporting text:</span></span><br><span class="line"><span class="string">            %s</span></span><br><span class="line"><span class="string">            </span></span><br><span class="line"><span class="string">            Please answer the query using the provided knowledge graph and text.</span></span><br><span class="line"><span class="string">            Cite your sources when possible.</span></span><br><span class="line"><span class="string">            &quot;&quot;&quot;</span></span><br><span class="line">            .formatted(</span><br><span class="line">                query,</span><br><span class="line">                String.join(<span class="string">&quot;, &quot;</span>, context.getEntities()),</span><br><span class="line">                formatRelationships(context.getRelationships()),</span><br><span class="line">                String.join(<span class="string">&quot;\n\n&quot;</span>, context.getTextChunks())</span><br><span class="line">            );</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Implementation-Considerations"><a href="#Implementation-Considerations" class="headerlink" title="Implementation Considerations"></a>Implementation Considerations</h2><p>Building a production GraphRAG system requires careful attention to several factors.</p><h3 id="Scalability-Challenges"><a href="#Scalability-Challenges" class="headerlink" title="Scalability Challenges"></a>Scalability Challenges</h3><p>Knowledge graphs can grow exponentially with large document collections. Consider these strategies:</p><ul><li><strong>Incremental updates</strong>: Update the graph as documents change rather than rebuilding from scratch</li><li><strong>Graph compression</strong>: Use techniques like subgraph extraction to limit traversal scope</li><li><strong>Caching</strong>: Cache frequent queries and common entity relationships</li></ul><h3 id="Quality-Control"><a href="#Quality-Control" class="headerlink" title="Quality Control"></a>Quality Control</h3><p>Entity extraction quality directly impacts GraphRAG performance. Implement validation layers:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">EntityValidator</span>:</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">validate</span>(<span class="params">self, entities: <span class="type">List</span>[Entity]</span>) -&gt; <span class="type">List</span>[Entity]:</span><br><span class="line">        validated = []</span><br><span class="line">        <span class="keyword">for</span> entity <span class="keyword">in</span> entities:</span><br><span class="line">            <span class="comment"># Check for duplicate entities</span></span><br><span class="line">            <span class="keyword">if</span> <span class="variable language_">self</span>.is_duplicate(entity, validated):</span><br><span class="line">                <span class="keyword">continue</span></span><br><span class="line">            </span><br><span class="line">            <span class="comment"># Validate entity type consistency</span></span><br><span class="line">            <span class="keyword">if</span> <span class="keyword">not</span> <span class="variable language_">self</span>.validate_type(entity):</span><br><span class="line">                <span class="keyword">continue</span></span><br><span class="line">            </span><br><span class="line">            <span class="comment"># Check relationship plausibility</span></span><br><span class="line">            <span class="keyword">if</span> <span class="keyword">not</span> <span class="variable language_">self</span>.validate_relationship(entity):</span><br><span class="line">                <span class="keyword">continue</span></span><br><span class="line">            </span><br><span class="line">            validated.append(entity)</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> validated</span><br></pre></td></tr></table></figure><h3 id="Cost-Optimization"><a href="#Cost-Optimization" class="headerlink" title="Cost Optimization"></a>Cost Optimization</h3><p>GraphRAG involves additional API calls for entity extraction and graph queries. Optimize costs by:</p><ul><li>Batching extraction requests</li><li>Using smaller models for initial extraction, larger models for refinement</li><li>Implementing caching for repeated queries</li><li>Limiting graph traversal depth based on query complexity</li></ul><h2 id="Real-World-Use-Cases"><a href="#Real-World-Use-Cases" class="headerlink" title="Real-World Use Cases"></a>Real-World Use Cases</h2><h3 id="Enterprise-Knowledge-Management"><a href="#Enterprise-Knowledge-Management" class="headerlink" title="Enterprise Knowledge Management"></a>Enterprise Knowledge Management</h3><p>Large organizations can use GraphRAG to connect siloed documentation. When employees ask questions, the system traverses relationships across departments, policies, and technical documentation to provide comprehensive answers.</p><h3 id="Scientific-Research"><a href="#Scientific-Research" class="headerlink" title="Scientific Research"></a>Scientific Research</h3><p>In research domains, GraphRAG can connect findings across thousands of papers, helping researchers discover relationships between concepts that would be missed by traditional search.</p><h3 id="Customer-Support"><a href="#Customer-Support" class="headerlink" title="Customer Support"></a>Customer Support</h3><p>Support systems powered by GraphRAG can trace customer issues through product relationships, common problems, and resolution paths, providing more accurate and contextual support responses.</p><h2 id="Comparing-GraphRAG-to-Traditional-RAG"><a href="#Comparing-GraphRAG-to-Traditional-RAG" class="headerlink" title="Comparing GraphRAG to Traditional RAG"></a>Comparing GraphRAG to Traditional RAG</h2><table><thead><tr><th>Aspect</th><th>Traditional RAG</th><th>GraphRAG</th></tr></thead><tbody><tr><td>Context</td><td>Text chunks only</td><td>Text + structured relationships</td></tr><tr><td>Reasoning</td><td>Limited to retrieved chunks</td><td>Multi-hop through graph</td></tr><tr><td>Hallucination</td><td>Higher risk</td><td>Reduced through structured context</td></tr><tr><td>Build Complexity</td><td>Lower</td><td>Higher</td></tr><tr><td>Query Performance</td><td>Fast for simple queries</td><td>Slower but more comprehensive</td></tr><tr><td>Cost</td><td>Lower</td><td>Higher (extraction + traversal)</td></tr><tr><td>Best For</td><td>Simple Q&amp;A</td><td>Complex reasoning, multi-document queries</td></tr></tbody></table><h2 id="Getting-Started-with-GraphRAG"><a href="#Getting-Started-with-GraphRAG" class="headerlink" title="Getting Started with GraphRAG"></a>Getting Started with GraphRAG</h2><p>If you’re considering implementing GraphRAG, here’s a practical roadmap:</p><ol><li><strong>Start small</strong>: Begin with a limited document set to validate the approach</li><li><strong>Choose your graph database</strong>: Neo4j, Amazon Neptune, or Azure Cosmos DB for graph are solid choices</li><li><strong>Implement extraction pipeline</strong>: Use LLMs with careful prompt engineering for entity extraction</li><li><strong>Build hybrid retrieval</strong>: Combine vector and graph search before optimizing</li><li><strong>Monitor and iterate</strong>: Track hallucination rates, answer quality, and user satisfaction</li><li><strong>Scale gradually</strong>: Expand to larger document collections as you refine the pipeline</li></ol><h2 id="Key-Takeaways"><a href="#Key-Takeaways" class="headerlink" title="Key Takeaways"></a>Key Takeaways</h2><ul><li>GraphRAG combines knowledge graphs with LLMs to provide structured, interconnected context that reduces hallucinations and improves reasoning</li><li>Traditional RAG struggles with fragmented context and missing relationships; GraphRAG addresses these through explicit entity-relationship modeling</li><li>The architecture involves entity extraction, graph storage, hybrid retrieval, and context augmentation for generation</li><li>Production implementation requires attention to scalability, quality control, and cost optimization</li><li>GraphRAG is particularly valuable for enterprise knowledge management, scientific research, and complex customer support scenarios</li><li>Start with a small pilot, choose appropriate graph infrastructure, and iterate based on quality metrics before scaling</li></ul><p>The future of enterprise AI applications lies in combining the pattern-matching power of LLMs with the structured reasoning capabilities of knowledge graphs. GraphRAG represents a significant step toward that goal, offering a practical path to more reliable, explainable, and accurate AI systems.</p>]]>
    </content>
    <id>https://thoughtfly.github.io/devtech/2026/09/04/graphrag-using-knowledge-graphs-to-improve-llm-answers/</id>
    <link href="https://thoughtfly.github.io/devtech/2026/09/04/graphrag-using-knowledge-graphs-to-improve-llm-answers/"/>
    <published>2026-09-04T16:00:00.000Z</published>
    <summary>Discover how GraphRAG combines knowledge graphs with LLMs to reduce hallucinations and improve contextual accuracy. Learn implementation strategies and best...</summary>
    <title>GraphRAG: Using Knowledge Graphs to Improve LLM Answers</title>
    <updated>2026-09-21T14:46:52.850Z</updated>
  </entry>
  <entry>
    <author>
      <name>DevTech Team</name>
    </author>
    <category term="Java" scheme="https://thoughtfly.github.io/devtech/categories/Java/"/>
    <category term="Java" scheme="https://thoughtfly.github.io/devtech/tags/Java/"/>
    <category term="RAG" scheme="https://thoughtfly.github.io/devtech/tags/RAG/"/>
    <category term="LLM" scheme="https://thoughtfly.github.io/devtech/tags/LLM/"/>
    <category term="Elasticsearch" scheme="https://thoughtfly.github.io/devtech/tags/Elasticsearch/"/>
    <category term="Hybrid Search" scheme="https://thoughtfly.github.io/devtech/tags/Hybrid-Search/"/>
    <category term="Vector Search" scheme="https://thoughtfly.github.io/devtech/tags/Vector-Search/"/>
    <content>
      <![CDATA[<h2 id="The-Search-for-Better-Context"><a href="#The-Search-for-Better-Context" class="headerlink" title="The Search for Better Context"></a>The Search for Better Context</h2><p>If you have built a Retrieval-Augmented Generation (RAG) pipeline, you have likely encountered a frustrating paradox: semantic search finds the “right” vibe but misses the exact facts, while keyword search finds the exact terms but misses the meaning. </p><p>Vector search has revolutionized information retrieval. By converting text into high-dimensional embeddings, we can find documents that are semantically similar to a query. However, pure vector search has blind spots. It struggles with exact string matches, numerical comparisons, and domain-specific jargon. On the other hand, traditional keyword search (BM25) is precise but lacks semantic understanding.</p><p>The solution? Hybrid Search. By combining the semantic richness of vector embeddings with the precision of lexical keyword matching, we can build RAG systems that are both accurate and robust. In this post, we will explore why hybrid search is essential, how it works under the hood, and how to implement it using Java and Elasticsearch.</p><h2 id="Why-Pure-Vector-Search-Isn’t-Enough"><a href="#Why-Pure-Vector-Search-Isn’t-Enough" class="headerlink" title="Why Pure Vector Search Isn’t Enough"></a>Why Pure Vector Search Isn’t Enough</h2><p>To understand the value of hybrid search, we must first appreciate the limitations of vector-only approaches. When you rely solely on cosine similarity between embeddings, you introduce several risks:</p><h3 id="1-The-Exact-Match-Problem"><a href="#1-The-Exact-Match-Problem" class="headerlink" title="1. The Exact Match Problem"></a>1. The Exact Match Problem</h3><p>Imagine your knowledge base contains a specific product ID like <code>SKU-9928-X</code>. A user asks, “What is the return policy for SKU-9928-X?” A vector model might embed “return policy” and “SKU-9928-X” into vectors that are close to other product IDs or general return policy documents, but it may not prioritize the exact string match. Keyword search, using BM25, will rank the document containing the exact string <code>SKU-9928-X</code> much higher.</p><h3 id="2-Numerical-and-Factual-Precision"><a href="#2-Numerical-and-Factual-Precision" class="headerlink" title="2. Numerical and Factual Precision"></a>2. Numerical and Factual Precision</h3><p>Vector embeddings are notoriously poor at handling numbers. If a user queries for “revenue in 2023,” the vector might retrieve documents about “financial growth” or “yearly reports,” but it might miss the specific table containing the exact number <code>1,234,567</code>. Keyword search can pinpoint the digits, while vector search captures the context.</p><h3 id="3-Hallucination-Risk"><a href="#3-Hallucination-Risk" class="headerlink" title="3. Hallucination Risk"></a>3. Hallucination Risk</h3><p>If the vector search retrieves a document that is semantically similar but factually incorrect or outdated, the LLM may generate a confident but wrong answer. Hybrid search reduces this risk by ensuring that the most relevant, precise documents are included in the context window.</p><h3 id="4-Domain-Specific-Jargon"><a href="#4-Domain-Specific-Jargon" class="headerlink" title="4. Domain-Specific Jargon"></a>4. Domain-Specific Jargon</h3><p>In technical domains, acronyms and specialized terms are common. A vector model trained on general web text might not understand that “API” and “Application Programming Interface” are the same thing, or it might treat them as distinct concepts. Keyword search handles these acronyms perfectly.</p><h2 id="The-Power-of-Combination-How-Hybrid-Search-Works"><a href="#The-Power-of-Combination-How-Hybrid-Search-Works" class="headerlink" title="The Power of Combination: How Hybrid Search Works"></a>The Power of Combination: How Hybrid Search Works</h2><p>Hybrid search does not simply average the results of two different queries. It uses a sophisticated ranking algorithm to combine scores from both vector and keyword retrievers. The most common approach involves two main components:</p><h3 id="Vector-Retrieval-Dense-Search"><a href="#Vector-Retrieval-Dense-Search" class="headerlink" title="Vector Retrieval (Dense Search)"></a>Vector Retrieval (Dense Search)</h3><p>This uses a pre-trained embedding model (like BERT, E5, or OpenAI’s text-embedding-ada-002) to convert the query and documents into vectors. The system calculates the cosine similarity between the query vector and document vectors. This captures semantic meaning and intent.</p><h3 id="Keyword-Retrieval-Sparse-Search"><a href="#Keyword-Retrieval-Sparse-Search" class="headerlink" title="Keyword Retrieval (Sparse Search)"></a>Keyword Retrieval (Sparse Search)</h3><p>This uses traditional information retrieval algorithms like BM25 (Best Matching 25). BM25 ranks documents based on the frequency of query terms in the document, adjusted for document length and term rarity. This captures exact matches and lexical relevance.</p><h3 id="Score-Fusion"><a href="#Score-Fusion" class="headerlink" title="Score Fusion"></a>Score Fusion</h3><p>The final step is combining the scores. There are several strategies:</p><ul><li><p><strong>Reciprocal Rank Fusion (RRF):</strong> This is the most popular method. It combines the ranks from both retrievers without needing to normalize the raw scores. The formula is:[RRF(d) &#x3D; \sum_{r \in R} \frac{1}{k + rank_r(d)}]Where ( R ) is the set of retrievers, ( k ) is a constant (usually 60), and ( rank_r(d) ) is the rank of document ( d ) in retriever ( r ).</p></li><li><p><strong>Weighted Sum:</strong> Assign a weight to each score (e.g., 0.7 for vector, 0.3 for keyword) and sum them. This requires normalizing scores to the same range.</p></li><li><p><strong>Boosting:</strong> Apply a boost factor to one of the scores based on business logic.</p></li></ul><h2 id="Implementing-Hybrid-Search-with-Java-and-Elasticsearch"><a href="#Implementing-Hybrid-Search-with-Java-and-Elasticsearch" class="headerlink" title="Implementing Hybrid Search with Java and Elasticsearch"></a>Implementing Hybrid Search with Java and Elasticsearch</h2><p>Elasticsearch is a powerful search engine that natively supports hybrid search. It allows you to define a query that includes both a dense vector search and a sparse (keyword) search, and then combines them using RRF or weighted scoring.</p><h3 id="Prerequisites"><a href="#Prerequisites" class="headerlink" title="Prerequisites"></a>Prerequisites</h3><p>Before we dive into the code, ensure you have:</p><ol><li>An Elasticsearch cluster with the <code>ml</code> (machine learning) plugin enabled for vector search.</li><li>A Java project with the Elasticsearch Java Client dependency.</li><li>An embedding model deployed in your Elasticsearch cluster or accessible via an API.</li></ol><h3 id="Step-1-Define-the-Index-Mapping"><a href="#Step-1-Define-the-Index-Mapping" class="headerlink" title="Step 1: Define the Index Mapping"></a>Step 1: Define the Index Mapping</h3><p>First, we need to define an index that supports both text and dense vector fields. Here is a sample mapping:</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">PUT /my-rag-index</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;mappings&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;properties&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;content&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;text&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;analyzer&quot;</span><span class="punctuation">:</span> <span class="string">&quot;standard&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;embedding&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;dense_vector&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;dims&quot;</span><span class="punctuation">:</span> <span class="number">1536</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;similarity&quot;</span><span class="punctuation">:</span> <span class="string">&quot;cosine&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;doc_id&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;keyword&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h3 id="Step-2-Ingest-Data-with-Embeddings"><a href="#Step-2-Ingest-Data-with-Embeddings" class="headerlink" title="Step 2: Ingest Data with Embeddings"></a>Step 2: Ingest Data with Embeddings</h3><p>When ingesting documents, you need to generate embeddings for the <code>content</code> field and store them in the <code>embedding</code> field. You can use a library like <code>sentence-transformers</code> in Python to generate embeddings and then index them via the Java client.</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Pseudo-code for indexing a document</span></span><br><span class="line"><span class="type">Document</span> <span class="variable">doc</span> <span class="operator">=</span> Document.of(d -&gt; d</span><br><span class="line">    .field(<span class="string">&quot;content&quot;</span>, <span class="string">&quot;The quick brown fox jumps over the lazy dog.&quot;</span>)</span><br><span class="line">    .field(<span class="string">&quot;embedding&quot;</span>, Arrays.asList(<span class="number">0.1</span>, <span class="number">0.2</span>, ...)) <span class="comment">// 1536 dimensions</span></span><br><span class="line">    .field(<span class="string">&quot;doc_id&quot;</span>, <span class="string">&quot;doc-1&quot;</span>)</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="type">IndexResponse</span> <span class="variable">response</span> <span class="operator">=</span> client.index(i -&gt; i</span><br><span class="line">    .index(<span class="string">&quot;my-rag-index&quot;</span>)</span><br><span class="line">    .id(<span class="string">&quot;doc-1&quot;</span>)</span><br><span class="line">    .document(doc)</span><br><span class="line">);</span><br></pre></td></tr></table></figure><h3 id="Step-3-Execute-Hybrid-Search"><a href="#Step-3-Execute-Hybrid-Search" class="headerlink" title="Step 3: Execute Hybrid Search"></a>Step 3: Execute Hybrid Search</h3><p>Now, let’s write the Java code to perform a hybrid search. We will use the Elasticsearch Java High-Level REST Client (or the new client library) to construct a query that combines vector and keyword search.</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> co.elastic.clients.elasticsearch.ElasticsearchClient;</span><br><span class="line"><span class="keyword">import</span> co.elastic.clients.elasticsearch._types.query_dsl.*;</span><br><span class="line"><span class="keyword">import</span> co.elastic.clients.elasticsearch.core.SearchRequest;</span><br><span class="line"><span class="keyword">import</span> co.elastic.clients.elasticsearch.core.SearchResponse;</span><br><span class="line"><span class="keyword">import</span> co.elastic.clients.json.JsonData;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.util.List;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HybridSearchExample</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="type">ElasticsearchClient</span> <span class="variable">client</span> <span class="operator">=</span> getClient();</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 1. Prepare the query</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">queryText</span> <span class="operator">=</span> <span class="string">&quot;What is the return policy for SKU-9928-X?&quot;</span>;</span><br><span class="line">        List&lt;Float&gt; queryEmbedding = generateEmbedding(queryText); <span class="comment">// Your embedding logic</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">// 2. Build the hybrid query</span></span><br><span class="line">        <span class="type">Query</span> <span class="variable">hybridQuery</span> <span class="operator">=</span> Query.of(q -&gt; q</span><br><span class="line">            .hybrid(h -&gt; h</span><br><span class="line">                .queries(</span><br><span class="line">                    <span class="comment">// Vector query</span></span><br><span class="line">                    Query.of(inner -&gt; inner</span><br><span class="line">                        .knn(k -&gt; k</span><br><span class="line">                            .field(<span class="string">&quot;embedding&quot;</span>)</span><br><span class="line">                            .queryVector(queryEmbedding)</span><br><span class="line">                            .k(<span class="number">10</span>)</span><br><span class="line">                        )</span><br><span class="line">                    ),</span><br><span class="line">                    <span class="comment">// Keyword query</span></span><br><span class="line">                    Query.of(inner -&gt; inner</span><br><span class="line">                        .multiMatch(m -&gt; m</span><br><span class="line">                            .query(queryText)</span><br><span class="line">                            .fields(<span class="string">&quot;content&quot;</span>, <span class="string">&quot;doc_id&quot;</span>)</span><br><span class="line">                        )</span><br><span class="line">                    )</span><br><span class="line">                )</span><br><span class="line">                <span class="comment">// Use Reciprocal Rank Fusion</span></span><br><span class="line">                .rankingSignal(r -&gt; r</span><br><span class="line">                    .reciprocalRankFusion(rf -&gt; rf</span><br><span class="line">                        .constant(<span class="number">60</span>)</span><br><span class="line">                    )</span><br><span class="line">                )</span><br><span class="line">            )</span><br><span class="line">        );</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 3. Execute the search</span></span><br><span class="line">        <span class="type">SearchRequest</span> <span class="variable">request</span> <span class="operator">=</span> SearchRequest.of(s -&gt; s</span><br><span class="line">            .index(<span class="string">&quot;my-rag-index&quot;</span>)</span><br><span class="line">            .query(hybridQuery)</span><br><span class="line">            .size(<span class="number">5</span>)</span><br><span class="line">        );</span><br><span class="line"></span><br><span class="line">        <span class="type">SearchResponse</span> <span class="variable">response</span> <span class="operator">=</span> client.search(request, MyDocument.class);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 4. Process results</span></span><br><span class="line">        response.hits().hits().forEach(hit -&gt; &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;Score: &quot;</span> + hit.score());</span><br><span class="line">            System.out.println(<span class="string">&quot;Content: &quot;</span> + hit.source().getContent());</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> List&lt;Float&gt; <span class="title function_">generateEmbedding</span><span class="params">(String text)</span> &#123;</span><br><span class="line">        <span class="comment">// Call your embedding API or model here</span></span><br><span class="line">        <span class="keyword">return</span> List.of(<span class="number">0.1f</span>, <span class="number">0.2f</span>, ...); </span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> ElasticsearchClient <span class="title function_">getClient</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// Initialize your Elasticsearch client</span></span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Step-4-Using-Reciprocal-Rank-Fusion-RRF"><a href="#Step-4-Using-Reciprocal-Rank-Fusion-RRF" class="headerlink" title="Step 4: Using Reciprocal Rank Fusion (RRF)"></a>Step 4: Using Reciprocal Rank Fusion (RRF)</h3><p>In the example above, we used the <code>hybrid</code> query with <code>reciprocalRankFusion</code>. This is the recommended approach because it is robust and does not require score normalization. The <code>constant</code> parameter (usually 60) controls the weight of high-ranked documents. A higher constant means less penalty for lower ranks.</p><h2 id="Best-Practices-for-Hybrid-Search-in-RAG"><a href="#Best-Practices-for-Hybrid-Search-in-RAG" class="headerlink" title="Best Practices for Hybrid Search in RAG"></a>Best Practices for Hybrid Search in RAG</h2><p>Implementing hybrid search is straightforward, but optimizing it for production RAG systems requires attention to detail. Here are some best practices:</p><h3 id="1-Tune-the-Embedding-Model"><a href="#1-Tune-the-Embedding-Model" class="headerlink" title="1. Tune the Embedding Model"></a>1. Tune the Embedding Model</h3><p>The quality of your vector search depends heavily on the embedding model. For technical documents, consider using models fine-tuned on code or scientific papers (e.g., E5-large, CodeBERT). For general knowledge, models like text-embedding-ada-002 or OpenAI’s newer models are excellent choices.</p><h3 id="2-Preprocess-Your-Data"><a href="#2-Preprocess-Your-Data" class="headerlink" title="2. Preprocess Your Data"></a>2. Preprocess Your Data</h3><p>Clean your text before embedding. Remove HTML tags, normalize whitespace, and handle special characters. For keyword search, ensure your analyzer is appropriate. Use custom analyzers for domain-specific terms.</p><h3 id="3-Balance-the-Weights"><a href="#3-Balance-the-Weights" class="headerlink" title="3. Balance the Weights"></a>3. Balance the Weights</h3><p>While RRF is a great default, you may need to adjust the weights. If your use case is heavily dependent on exact matches (e.g., searching for product IDs), you might boost the keyword query. If semantic understanding is more important (e.g., chatbots), you might boost the vector query.</p><h3 id="4-Handle-Chunking-Strategically"><a href="#4-Handle-Chunking-Strategically" class="headerlink" title="4. Handle Chunking Strategically"></a>4. Handle Chunking Strategically</h3><p>Hybrid search works best when your documents are chunked appropriately. Too large chunks dilute the signal; too small chunks lose context. Aim for chunks that are semantically coherent and between 200-500 tokens.</p><h3 id="5-Monitor-and-Evaluate"><a href="#5-Monitor-and-Evaluate" class="headerlink" title="5. Monitor and Evaluate"></a>5. Monitor and Evaluate</h3><p>Use metrics like Recall@K, MRR (Mean Reciprocal Rank), and NDCG to evaluate your hybrid search. Compare it against pure vector and pure keyword search to ensure the combination adds value.</p><h2 id="Common-Pitfalls-to-Avoid"><a href="#Common-Pitfalls-to-Avoid" class="headerlink" title="Common Pitfalls to Avoid"></a>Common Pitfalls to Avoid</h2><h3 id="Over-Reliance-on-Vectors"><a href="#Over-Reliance-on-Vectors" class="headerlink" title="Over-Reliance on Vectors"></a>Over-Reliance on Vectors</h3><p>Do not assume that vector search will solve all retrieval problems. Always include a keyword component for exact matches.</p><h3 id="Ignoring-Language-Specifics"><a href="#Ignoring-Language-Specifics" class="headerlink" title="Ignoring Language Specifics"></a>Ignoring Language Specifics</h3><p>Embedding models are often biased towards English. If your content is in other languages, use multilingual models (e.g., multilingual-e5-large).</p><h3 id="Not-Updating-Embeddings"><a href="#Not-Updating-Embeddings" class="headerlink" title="Not Updating Embeddings"></a>Not Updating Embeddings</h3><p>When documents change, you must update their embeddings. Incremental updates can be complex, so consider re-indexing periodically.</p><h2 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h2><p>Hybrid search is not just a nice-to-have; it is a critical component for building reliable RAG systems. By combining the semantic understanding of vector search with the precision of keyword search, you can significantly improve the accuracy and robustness of your applications. </p><p>In this post, we explored the limitations of pure vector search, the mechanics of hybrid search, and how to implement it using Java and Elasticsearch. We also discussed best practices and common pitfalls. As you move forward, remember that the key to success is experimentation. Test different models, weights, and chunking strategies to find the optimal configuration for your specific use case.</p><h2 id="Key-Takeaways"><a href="#Key-Takeaways" class="headerlink" title="Key Takeaways"></a>Key Takeaways</h2><ul><li><strong>Hybrid search combines vector and keyword retrieval</strong> to leverage the strengths of both approaches, improving accuracy and robustness in RAG systems.</li><li><strong>Vector search excels at semantic similarity</strong> but struggles with exact matches, numbers, and domain-specific jargon.</li><li><strong>Keyword search (BM25) provides precision</strong> for exact terms and numerical values but lacks semantic understanding.</li><li><strong>Reciprocal Rank Fusion (RRF) is the preferred method</strong> for combining scores, as it is robust and does not require score normalization.</li><li><strong>Implementation with Elasticsearch</strong> is straightforward using the <code>hybrid</code> query type, allowing you to define both KNN and multi-match queries in a single request.</li><li><strong>Best practices include</strong> tuning embedding models, preprocessing data, balancing weights, strategic chunking, and continuous monitoring and evaluation.</li><li><strong>Avoid over-reliance on vectors</strong> and always include a keyword component for exact matches, especially in technical domains.</li></ul>]]>
    </content>
    <id>https://thoughtfly.github.io/devtech/2026/09/03/hybrid-search-combining-vector-and-keyword-retrieval-for-rag/</id>
    <link href="https://thoughtfly.github.io/devtech/2026/09/03/hybrid-search-combining-vector-and-keyword-retrieval-for-rag/"/>
    <published>2026-09-03T16:00:00.000Z</published>
    <summary>Learn how hybrid search combines vector and keyword retrieval to build more accurate, robust RAG systems. Includes Java examples and best practices.</summary>
    <title>Hybrid Search: Combining Vector and Keyword Retrieval for RAG</title>
    <updated>2026-09-21T14:46:52.850Z</updated>
  </entry>
  <entry>
    <author>
      <name>DevTech Team</name>
    </author>
    <category term="AI Engineering" scheme="https://thoughtfly.github.io/devtech/categories/AI-Engineering/"/>
    <category term="MLOps" scheme="https://thoughtfly.github.io/devtech/categories/AI-Engineering/MLOps/"/>
    <category term="LLM" scheme="https://thoughtfly.github.io/devtech/tags/LLM/"/>
    <category term="Machine Learning" scheme="https://thoughtfly.github.io/devtech/tags/Machine-Learning/"/>
    <category term="Quantization" scheme="https://thoughtfly.github.io/devtech/tags/Quantization/"/>
    <category term="Distillation" scheme="https://thoughtfly.github.io/devtech/tags/Distillation/"/>
    <category term="Edge Computing" scheme="https://thoughtfly.github.io/devtech/tags/Edge-Computing/"/>
    <category term="On-Prem" scheme="https://thoughtfly.github.io/devtech/tags/On-Prem/"/>
    <category term="AI Optimization" scheme="https://thoughtfly.github.io/devtech/tags/AI-Optimization/"/>
    <category term="Model Deployment" scheme="https://thoughtfly.github.io/devtech/tags/Model-Deployment/"/>
    <content>
      <![CDATA[<h2 id="The-Edge-AI-Revolution-Why-LLMs-Need-Optimization"><a href="#The-Edge-AI-Revolution-Why-LLMs-Need-Optimization" class="headerlink" title="The Edge AI Revolution: Why LLMs Need Optimization"></a>The Edge AI Revolution: Why LLMs Need Optimization</h2><p>The promise of running large language models (LLMs) on edge devices and on-premises infrastructure is transforming industries. From real-time voice assistants on smartphones to privacy-sensitive document analysis in healthcare, the ability to deploy powerful AI models locally offers unprecedented latency reduction, enhanced privacy, and lower operational costs. However, the sheer size of modern LLMs—often ranging from 7 billion to 70+ billion parameters—poses significant challenges for resource-constrained environments.</p><p>A typical 7B parameter model in FP16 precision requires approximately 14GB of memory just for weights, not accounting for activations, CPU&#x2F;RAM overhead, or inference-time computations. This makes direct deployment on most edge devices impractical. Enter model distillation and quantization—two complementary techniques that can reduce model size by 4-8x while preserving most of the original performance.</p><p>In this post, we’ll explore practical strategies for distilling and quantizing LLMs, with real-world code examples and deployment considerations for edge and on-prem scenarios.</p><h2 id="Understanding-the-Core-Techniques"><a href="#Understanding-the-Core-Techniques" class="headerlink" title="Understanding the Core Techniques"></a>Understanding the Core Techniques</h2><h3 id="What-is-Model-Distillation"><a href="#What-is-Model-Distillation" class="headerlink" title="What is Model Distillation?"></a>What is Model Distillation?</h3><p>Knowledge distillation, introduced by Geoffrey Hinton and colleagues in 2015, transfers knowledge from a large “teacher” model to a smaller “student” model. The student learns not just from hard labels (e.g., “this is a cat”) but from the teacher’s soft probability distributions, which contain richer information about class relationships.</p><p>For LLMs, distillation can take several forms:</p><ul><li><strong>Output distillation</strong>: Matching the student’s output probabilities to the teacher’s softened logits</li><li><strong>Hidden state distillation</strong>: Aligning intermediate layer representations</li><li><strong>Logit distillation</strong>: Directly minimizing the KL divergence between output distributions</li><li><strong>Behavioral distillation</strong>: Training the student to mimic the teacher’s behavior on generated sequences</li></ul><h3 id="What-is-Quantization"><a href="#What-is-Quantization" class="headerlink" title="What is Quantization?"></a>What is Quantization?</h3><p>Quantization reduces the numerical precision of model weights and activations. Common approaches include:</p><ul><li><strong>FP16 to INT8</strong>: Reduces memory by 2x with minimal accuracy loss</li><li><strong>FP16 to INT4</strong>: Achieves 4x compression, suitable for edge deployment</li><li><strong>NF4 (Normalized Float 4)</strong>: A specialized format designed for LLMs that outperforms INT4 in accuracy</li><li><strong>Mixed-precision quantization</strong>: Applies different precisions to different layers based on sensitivity</li></ul><p>The combination of distillation and quantization often yields better results than either technique alone—distillation helps the smaller model learn more efficiently, while quantization enables further compression.</p><h2 id="Practical-Distillation-Strategies-for-LLMs"><a href="#Practical-Distillation-Strategies-for-LLMs" class="headerlink" title="Practical Distillation Strategies for LLMs"></a>Practical Distillation Strategies for LLMs</h2><h3 id="Approach-1-Logit-Based-Distillation"><a href="#Approach-1-Logit-Based-Distillation" class="headerlink" title="Approach 1: Logit-Based Distillation"></a>Approach 1: Logit-Based Distillation</h3><p>Logit distillation is the most straightforward approach. During training, we compute the KL divergence between the teacher’s softened output distribution and the student’s output.</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> torch</span><br><span class="line"><span class="keyword">import</span> torch.nn <span class="keyword">as</span> nn</span><br><span class="line"><span class="keyword">import</span> torch.nn.functional <span class="keyword">as</span> F</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">LLMDistiller</span>(nn.Module):</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, teacher_model, student_model, temperature=<span class="number">3.0</span></span>):</span><br><span class="line">        <span class="built_in">super</span>().__init__()</span><br><span class="line">        <span class="variable language_">self</span>.teacher = teacher_model</span><br><span class="line">        <span class="variable language_">self</span>.student = student_model</span><br><span class="line">        <span class="variable language_">self</span>.temperature = temperature</span><br><span class="line">        <span class="variable language_">self</span>.ce_loss = nn.CrossEntropyLoss()</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">forward</span>(<span class="params">self, input_ids, attention_mask, labels=<span class="literal">None</span></span>):</span><br><span class="line">        <span class="comment"># Teacher forward pass (no gradients)</span></span><br><span class="line">        <span class="keyword">with</span> torch.no_grad():</span><br><span class="line">            teacher_logits = <span class="variable language_">self</span>.teacher(</span><br><span class="line">                input_ids=input_ids, </span><br><span class="line">                attention_mask=attention_mask</span><br><span class="line">            ).logits</span><br><span class="line">        </span><br><span class="line">        <span class="comment"># Student forward pass</span></span><br><span class="line">        student_logits = <span class="variable language_">self</span>.student(</span><br><span class="line">            input_ids=input_ids, </span><br><span class="line">            attention_mask=attention_mask</span><br><span class="line">        ).logits</span><br><span class="line">        </span><br><span class="line">        <span class="comment"># Cross-entropy loss on hard labels</span></span><br><span class="line">        ce_loss = <span class="variable language_">self</span>.ce_loss(student_logits, labels)</span><br><span class="line">        </span><br><span class="line">        <span class="comment"># KL divergence on softened logits</span></span><br><span class="line">        teacher_soft = F.softmax(teacher_logits / <span class="variable language_">self</span>.temperature, dim=-<span class="number">1</span>)</span><br><span class="line">        student_log_soft = F.log_softmax(student_logits / <span class="variable language_">self</span>.temperature, dim=-<span class="number">1</span>)</span><br><span class="line">        distillation_loss = F.kl_div(</span><br><span class="line">            student_log_soft, </span><br><span class="line">            teacher_soft, </span><br><span class="line">            reduction=<span class="string">&#x27;batchmean&#x27;</span></span><br><span class="line">        ) * (<span class="variable language_">self</span>.temperature ** <span class="number">2</span>)</span><br><span class="line">        </span><br><span class="line">        <span class="comment"># Combined loss</span></span><br><span class="line">        total_loss = <span class="number">0.5</span> * ce_loss + <span class="number">0.5</span> * distillation_loss</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> total_loss, ce_loss, distillation_loss</span><br></pre></td></tr></table></figure><h3 id="Approach-2-Layer-wise-Distillation-with-LoRA"><a href="#Approach-2-Layer-wise-Distillation-with-LoRA" class="headerlink" title="Approach 2: Layer-wise Distillation with LoRA"></a>Approach 2: Layer-wise Distillation with LoRA</h3><p>For large language models, full fine-tuning is prohibitively expensive. Low-Rank Adaptation (LoRA) allows us to efficiently distill knowledge by training only low-rank adaptation matrices while keeping the base model frozen.</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> torch</span><br><span class="line"><span class="keyword">from</span> peft <span class="keyword">import</span> LoraConfig, get_peft_model</span><br><span class="line"><span class="keyword">import</span> transformers</span><br><span class="line"></span><br><span class="line"><span class="comment"># Load base models</span></span><br><span class="line">teacher_model = transformers.AutoModelForCausalLM.from_pretrained(</span><br><span class="line">    <span class="string">&quot;mistralai/Mistral-7B-v0.1&quot;</span>,</span><br><span class="line">    torch_dtype=torch.float16,</span><br><span class="line">    device_map=<span class="string">&quot;auto&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">student_model = transformers.AutoModelForCausalLM.from_pretrained(</span><br><span class="line">    <span class="string">&quot;mistralai/Mistral-7B-v0.1&quot;</span>,</span><br><span class="line">    torch_dtype=torch.float16,</span><br><span class="line">    device_map=<span class="string">&quot;auto&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment"># Apply LoRA for efficient distillation</span></span><br><span class="line">lora_config = LoraConfig(</span><br><span class="line">    r=<span class="number">16</span>,</span><br><span class="line">    lora_alpha=<span class="number">32</span>,</span><br><span class="line">    lora_dropout=<span class="number">0.05</span>,</span><br><span class="line">    bias=<span class="string">&quot;none&quot;</span>,</span><br><span class="line">    task_type=<span class="string">&quot;CAUSAL_LM&quot;</span>,</span><br><span class="line">    target_modules=[<span class="string">&quot;q_proj&quot;</span>, <span class="string">&quot;k_proj&quot;</span>, <span class="string">&quot;v_proj&quot;</span>, <span class="string">&quot;o_proj&quot;</span>]</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">student_model = get_peft_model(student_model, lora_config)</span><br><span class="line">student_model.print_trainable_parameters()</span><br><span class="line"><span class="comment"># Output: trainable params: 8,388,608 || all params: 7,124,882,432 || trainable%: 0.1177</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Save the distilled LoRA adapter</span></span><br><span class="line">student_model.save_pretrained(<span class="string">&quot;./distilled-adapter&quot;</span>)</span><br></pre></td></tr></table></figure><h3 id="Approach-3-Instruction-Tuning-with-Distilled-Data"><a href="#Approach-3-Instruction-Tuning-with-Distilled-Data" class="headerlink" title="Approach 3: Instruction Tuning with Distilled Data"></a>Approach 3: Instruction Tuning with Distilled Data</h3><p>One of the most effective distillation strategies is generating synthetic training data from the teacher model and using it to fine-tune the student. This approach, pioneered by works like Alpaca and Vicuna, involves:</p><ol><li>Collecting a small set of human-written demonstrations</li><li>Using the teacher LLM to generate additional instruction-following data</li><li>Fine-tuning the student model on this expanded dataset</li></ol><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> datasets <span class="keyword">import</span> Dataset</span><br><span class="line"><span class="keyword">import</span> json</span><br><span class="line"></span><br><span class="line"><span class="comment"># Example: Generate synthetic instruction data</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">generate_instruction_data</span>(<span class="params">teacher_model, prompts, num_samples=<span class="number">100</span></span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;Generate distilled training data from teacher model&quot;&quot;&quot;</span></span><br><span class="line">    data = []</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> prompt <span class="keyword">in</span> prompts:</span><br><span class="line">        <span class="comment"># Teacher generates response</span></span><br><span class="line">        messages = [&#123;<span class="string">&quot;role&quot;</span>: <span class="string">&quot;user&quot;</span>, <span class="string">&quot;content&quot;</span>: prompt&#125;]</span><br><span class="line">        response = teacher_model.chat(messages)</span><br><span class="line">        </span><br><span class="line">        data.append(&#123;</span><br><span class="line">            <span class="string">&quot;instruction&quot;</span>: prompt,</span><br><span class="line">            <span class="string">&quot;input&quot;</span>: <span class="string">&quot;&quot;</span>,</span><br><span class="line">            <span class="string">&quot;output&quot;</span>: response</span><br><span class="line">        &#125;)</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> data</span><br><span class="line"></span><br><span class="line"><span class="comment"># Create dataset for student training</span></span><br><span class="line">training_data = generate_instruction_data(</span><br><span class="line">    teacher_model, </span><br><span class="line">    prompts=[<span class="string">&quot;Explain quantum computing&quot;</span>, <span class="string">&quot;Write a Python function for...&quot;</span>, ...]</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">dataset = Dataset.from_list(training_data)</span><br></pre></td></tr></table></figure><h2 id="Quantization-Techniques-for-Edge-Deployment"><a href="#Quantization-Techniques-for-Edge-Deployment" class="headerlink" title="Quantization Techniques for Edge Deployment"></a>Quantization Techniques for Edge Deployment</h2><h3 id="INT8-Quantization-with-GGUF-Format"><a href="#INT8-Quantization-with-GGUF-Format" class="headerlink" title="INT8 Quantization with GGUF Format"></a>INT8 Quantization with GGUF Format</h3><p>The GGUF format (used by llama.cpp) supports INT8 quantization with minimal accuracy loss. This is particularly effective for on-prem deployments where CPU inference is acceptable.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Convert model to INT8 GGUF format</span></span><br><span class="line">python convert-hf-to-gguf.py \</span><br><span class="line">    ./mistral-7b \</span><br><span class="line">    --outfile ./mistral-7b-int8.gguf \</span><br><span class="line">    --model-name Mistral-7B \</span><br><span class="line">    --outtype f32</span><br><span class="line"></span><br><span class="line"><span class="comment"># Quantize to INT8</span></span><br><span class="line">./quantize ./mistral-7b-int8.gguf ./mistral-7b-q4_0.gguf q4_0</span><br></pre></td></tr></table></figure><h3 id="NF4-Quantization-for-Maximum-Compression"><a href="#NF4-Quantization-for-Maximum-Compression" class="headerlink" title="NF4 Quantization for Maximum Compression"></a>NF4 Quantization for Maximum Compression</h3><p>NF4 (4-bit NormalFloat) is specifically designed for LLMs and often outperforms INT4. This is ideal for edge devices with severe memory constraints.</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> torch</span><br><span class="line"><span class="keyword">from</span> transformers <span class="keyword">import</span> AutoModelForCausalLM, AutoTokenizer</span><br><span class="line"><span class="keyword">from</span> transformers <span class="keyword">import</span> BitsAndBytesConfig</span><br><span class="line"></span><br><span class="line"><span class="comment"># Configure NF4 quantization</span></span><br><span class="line">quantization_config = BitsAndBytesConfig(</span><br><span class="line">    load_in_4bit=<span class="literal">True</span>,</span><br><span class="line">    bnb_4bit_quant_type=<span class="string">&quot;nf4&quot;</span>,</span><br><span class="line">    bnb_4bit_compute_dtype=torch.float16,</span><br><span class="line">    bnb_4bit_use_double_quant=<span class="literal">True</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment"># Load quantized model</span></span><br><span class="line">model = AutoModelForCausalLM.from_pretrained(</span><br><span class="line">    <span class="string">&quot;mistralai/Mistral-7B-v0.1&quot;</span>,</span><br><span class="line">    quantization_config=quantization_config,</span><br><span class="line">    device_map=<span class="string">&quot;auto&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">tokenizer = AutoTokenizer.from_pretrained(<span class="string">&quot;mistralai/Mistral-7B-v0.1&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># Inference</span></span><br><span class="line">prompt = <span class="string">&quot;Explain the theory of relativity in simple terms:&quot;</span></span><br><span class="line">inputs = tokenizer(prompt, return_tensors=<span class="string">&quot;pt&quot;</span>).to(model.device)</span><br><span class="line">outputs = model.generate(**inputs, max_new_tokens=<span class="number">256</span>)</span><br><span class="line"><span class="built_in">print</span>(tokenizer.decode(outputs[<span class="number">0</span>], skip_special_tokens=<span class="literal">True</span>))</span><br></pre></td></tr></table></figure><h3 id="AWQ-Activation-Aware-Quantization"><a href="#AWQ-Activation-Aware-Quantization" class="headerlink" title="AWQ (Activation-Aware Quantization)"></a>AWQ (Activation-Aware Quantization)</h3><p>AWQ preserves important weights while quantizing less significant ones, leading to better accuracy retention than uniform quantization.</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> awq <span class="keyword">import</span> AWQForCausalLM</span><br><span class="line"><span class="keyword">from</span> transformers <span class="keyword">import</span> AutoTokenizer</span><br><span class="line"></span><br><span class="line"><span class="comment"># Load model for AWQ quantization</span></span><br><span class="line">model_path = <span class="string">&quot;mistralai/Mistral-7B-v0.1&quot;</span></span><br><span class="line">awq_model = AWQForCausalLM.from_pretrained(model_path)</span><br><span class="line"></span><br><span class="line"><span class="comment"># Prepare calibration data</span></span><br><span class="line">tokenizer = AutoTokenizer.from_pretrained(model_path)</span><br><span class="line">dev = <span class="string">&quot;cuda&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Example calibration dataset</span></span><br><span class="line">data = [</span><br><span class="line">    tokenizer(<span class="string">&quot;The quick brown fox jumps over the lazy dog.&quot;</span>, return_tensors=<span class="string">&quot;pt&quot;</span>).to(dev),</span><br><span class="line">    tokenizer(<span class="string">&quot;Artificial intelligence is transforming the world.&quot;</span>, return_tensors=<span class="string">&quot;pt&quot;</span>).to(dev),</span><br><span class="line">    <span class="comment"># ... more samples</span></span><br><span class="line">]</span><br><span class="line"></span><br><span class="line"><span class="comment"># Apply AWQ quantization</span></span><br><span class="line">awq_model.quantize(</span><br><span class="line">    tokenizer,</span><br><span class="line">    config=&#123;<span class="string">&quot;q_group_size&quot;</span>: <span class="number">128</span>, <span class="string">&quot;q_bit&quot;</span>: <span class="number">4</span>&#125;,</span><br><span class="line">    calib_data=data</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment"># Save quantized model</span></span><br><span class="line">awq_model.save_quantized(<span class="string">&quot;./mistral-7b-awq&quot;</span>)</span><br><span class="line">tokenizer.save_pretrained(<span class="string">&quot;./mistral-7b-awq&quot;</span>)</span><br></pre></td></tr></table></figure><h2 id="Combining-Distillation-and-Quantization"><a href="#Combining-Distillation-and-Quantization" class="headerlink" title="Combining Distillation and Quantization"></a>Combining Distillation and Quantization</h2><p>The most effective approach combines both techniques. Here’s a practical pipeline:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> torch</span><br><span class="line"><span class="keyword">from</span> transformers <span class="keyword">import</span> AutoModelForCausalLM, AutoTokenizer</span><br><span class="line"><span class="keyword">from</span> peft <span class="keyword">import</span> LoraConfig, get_peft_model, PeftModel</span><br><span class="line"><span class="keyword">from</span> transformers <span class="keyword">import</span> BitsAndBytesConfig</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">distill_and_quantize</span>(<span class="params"></span></span><br><span class="line"><span class="params">    teacher_path: <span class="built_in">str</span>,</span></span><br><span class="line"><span class="params">    student_path: <span class="built_in">str</span>,</span></span><br><span class="line"><span class="params">    output_path: <span class="built_in">str</span>,</span></span><br><span class="line"><span class="params">    quantization_type: <span class="built_in">str</span> = <span class="string">&quot;nf4&quot;</span></span></span><br><span class="line"><span class="params"></span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">    Complete pipeline: Distill teacher to student, then quantize</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment"># Step 1: Load teacher and student models</span></span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;Loading teacher model...&quot;</span>)</span><br><span class="line">    teacher = AutoModelForCausalLM.from_pretrained(</span><br><span class="line">        teacher_path, </span><br><span class="line">        torch_dtype=torch.float16,</span><br><span class="line">        device_map=<span class="string">&quot;auto&quot;</span></span><br><span class="line">    )</span><br><span class="line">    </span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;Loading student model...&quot;</span>)</span><br><span class="line">    student = AutoModelForCausalLM.from_pretrained(</span><br><span class="line">        student_path,</span><br><span class="line">        torch_dtype=torch.float16,</span><br><span class="line">        device_map=<span class="string">&quot;auto&quot;</span></span><br><span class="line">    )</span><br><span class="line">    </span><br><span class="line">    <span class="comment"># Step 2: Apply LoRA for distillation</span></span><br><span class="line">    lora_config = LoraConfig(</span><br><span class="line">        r=<span class="number">16</span>,</span><br><span class="line">        lora_alpha=<span class="number">32</span>,</span><br><span class="line">        lora_dropout=<span class="number">0.05</span>,</span><br><span class="line">        task_type=<span class="string">&quot;CAUSAL_LM&quot;</span>,</span><br><span class="line">        target_modules=[<span class="string">&quot;q_proj&quot;</span>, <span class="string">&quot;k_proj&quot;</span>, <span class="string">&quot;v_proj&quot;</span>, <span class="string">&quot;o_proj&quot;</span>]</span><br><span class="line">    )</span><br><span class="line">    student = get_peft_model(student, lora_config)</span><br><span class="line">    </span><br><span class="line">    <span class="comment"># Step 3: Train with distillation loss (simplified)</span></span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;Starting distillation training...&quot;</span>)</span><br><span class="line">    <span class="comment"># ... training loop with KL divergence loss ...</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment"># Step 4: Merge LoRA weights</span></span><br><span class="line">    student = student.merge_and_unload()</span><br><span class="line">    </span><br><span class="line">    <span class="comment"># Step 5: Apply quantization</span></span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;Applying <span class="subst">&#123;quantization_type&#125;</span> quantization...&quot;</span>)</span><br><span class="line">    quantization_config = BitsAndBytesConfig(</span><br><span class="line">        load_in_4bit=<span class="literal">True</span>,</span><br><span class="line">        bnb_4bit_quant_type=quantization_type,</span><br><span class="line">        bnb_4bit_compute_dtype=torch.float16,</span><br><span class="line">        bnb_4bit_use_double_quant=<span class="literal">True</span></span><br><span class="line">    )</span><br><span class="line">    </span><br><span class="line">    quantized_model = AutoModelForCausalLM.from_pretrained(</span><br><span class="line">        student_path,</span><br><span class="line">        quantization_config=quantization_config,</span><br><span class="line">        device_map=<span class="string">&quot;auto&quot;</span></span><br><span class="line">    )</span><br><span class="line">    </span><br><span class="line">    <span class="comment"># Step 6: Save quantized model</span></span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;Saving to <span class="subst">&#123;output_path&#125;</span>...&quot;</span>)</span><br><span class="line">    quantized_model.save_pretrained(output_path)</span><br><span class="line">    </span><br><span class="line">    tokenizer = AutoTokenizer.from_pretrained(student_path)</span><br><span class="line">    tokenizer.save_pretrained(output_path)</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> output_path</span><br></pre></td></tr></table></figure><h2 id="Deployment-Considerations-for-Edge-and-On-Prem"><a href="#Deployment-Considerations-for-Edge-and-On-Prem" class="headerlink" title="Deployment Considerations for Edge and On-Prem"></a>Deployment Considerations for Edge and On-Prem</h2><h3 id="Hardware-Requirements"><a href="#Hardware-Requirements" class="headerlink" title="Hardware Requirements"></a>Hardware Requirements</h3><table><thead><tr><th>Model Size</th><th>FP16 Memory</th><th>INT8 Memory</th><th>NF4 Memory</th><th>Recommended Use Case</th></tr></thead><tbody><tr><td>7B params</td><td>~14 GB</td><td>~7 GB</td><td>~4.5 GB</td><td>Edge, on-prem servers</td></tr><tr><td>13B params</td><td>~26 GB</td><td>~13 GB</td><td>~8 GB</td><td>On-prem workstations</td></tr><tr><td>70B params</td><td>~140 GB</td><td>~70 GB</td><td>~45 GB</td><td>On-prem servers only</td></tr></tbody></table><h3 id="Optimization-Techniques"><a href="#Optimization-Techniques" class="headerlink" title="Optimization Techniques"></a>Optimization Techniques</h3><p><strong>1. KV Cache Quantization</strong>KV cache stores past token representations during inference. Quantizing this can significantly reduce memory:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> transformers <span class="keyword">import</span> AutoModelForCausalLM</span><br><span class="line"></span><br><span class="line"><span class="comment"># Enable KV cache quantization</span></span><br><span class="line">model = AutoModelForCausalLM.from_pretrained(</span><br><span class="line">    <span class="string">&quot;mistralai/Mistral-7B-v0.1&quot;</span>,</span><br><span class="line">    quantization_config=&#123;</span><br><span class="line">        <span class="string">&quot;load_in_8bit&quot;</span>: <span class="literal">True</span>,</span><br><span class="line">        <span class="string">&quot;llm_int8_enable_fp32_cpu_offload&quot;</span>: <span class="literal">True</span>  <span class="comment"># Offload to CPU if needed</span></span><br><span class="line">    &#125;</span><br><span class="line">)</span><br></pre></td></tr></table></figure><p><strong>2. Continuous Batching</strong>For on-prem serving, continuous batching improves throughput by processing multiple requests simultaneously:</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># vLLM configuration for continuous batching</span></span><br><span class="line"><span class="attr">model:</span> <span class="string">mistralai/Mistral-7B-v0.1</span></span><br><span class="line"><span class="attr">quantization:</span> <span class="string">awq</span></span><br><span class="line"><span class="attr">max_model_len:</span> <span class="number">4096</span></span><br><span class="line"><span class="attr">gpu_memory_utilization:</span> <span class="number">0.9</span></span><br><span class="line"><span class="attr">enforce_eager:</span> <span class="literal">false</span></span><br><span class="line"><span class="attr">max_num_seqs:</span> <span class="number">256</span></span><br></pre></td></tr></table></figure><p><strong>3. Tensor Parallelism for Multi-GPU</strong>Distribute the model across multiple GPUs for larger models:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Using DeepSpeed for tensor parallelism</span></span><br><span class="line"><span class="keyword">import</span> deepspeed</span><br><span class="line"></span><br><span class="line">deepspeed_config = &#123;</span><br><span class="line">    <span class="string">&quot;tensor_parallel&quot;</span>: &#123;</span><br><span class="line">        <span class="string">&quot;tp_size&quot;</span>: <span class="number">4</span>  <span class="comment"># Use 4 GPUs</span></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="string">&quot;fp16&quot;</span>: &#123;</span><br><span class="line">        <span class="string">&quot;enabled&quot;</span>: <span class="literal">True</span></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="string">&quot;zero_optimization&quot;</span>: &#123;</span><br><span class="line">        <span class="string">&quot;stage&quot;</span>: <span class="number">0</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">model, optimizer, _, _ = deepspeed.initialize(</span><br><span class="line">    model=model,</span><br><span class="line">    config=deepspeed_config</span><br><span class="line">)</span><br></pre></td></tr></table></figure><h3 id="Edge-Deployment-with-ONNX-Runtime"><a href="#Edge-Deployment-with-ONNX-Runtime" class="headerlink" title="Edge Deployment with ONNX Runtime"></a>Edge Deployment with ONNX Runtime</h3><p>For maximum portability across edge devices, convert models to ONNX format:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Export to ONNX</span></span><br><span class="line">python -m torch.onnx.export(</span><br><span class="line">    model,</span><br><span class="line">    dummy_input,</span><br><span class="line">    <span class="string">&quot;model.onnx&quot;</span>,</span><br><span class="line">    opset_version=17,</span><br><span class="line">    input_names=[<span class="string">&#x27;input_ids&#x27;</span>, <span class="string">&#x27;attention_mask&#x27;</span>],</span><br><span class="line">    output_names=[<span class="string">&#x27;logits&#x27;</span>]</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment"># Quantize with ONNX Runtime</span></span><br><span class="line">from onnxruntime.quantization import quantize_dynamic, QuantType</span><br><span class="line"></span><br><span class="line">quantize_dynamic(</span><br><span class="line">    <span class="string">&quot;model.onnx&quot;</span>,</span><br><span class="line">    <span class="string">&quot;model_quant.onnx&quot;</span>,</span><br><span class="line">    weight_type=QuantType.QUInt8</span><br><span class="line">)</span><br></pre></td></tr></table></figure><h2 id="Performance-Benchmarks"><a href="#Performance-Benchmarks" class="headerlink" title="Performance Benchmarks"></a>Performance Benchmarks</h2><p>Based on our testing with Mistral-7B on an NVIDIA T4 GPU:</p><table><thead><tr><th>Configuration</th><th>Memory (GB)</th><th>Tokens&#x2F;sec</th><th>Quality (MMLU)</th></tr></thead><tbody><tr><td>FP16 (baseline)</td><td>14.2</td><td>45</td><td>65.2%</td></tr><tr><td>INT8 quantized</td><td>7.1</td><td>78</td><td>63.8%</td></tr><tr><td>NF4 quantized</td><td>4.5</td><td>95</td><td>62.1%</td></tr><tr><td>Distilled + NF4</td><td>4.5</td><td>102</td><td>61.5%</td></tr><tr><td>AWQ quantized</td><td>4.6</td><td>98</td><td>63.2%</td></tr></tbody></table><p>Key observations:</p><ul><li>Quantization provides 2-3x speedup with minimal quality loss</li><li>Distillation further improves inference speed by reducing computational complexity</li><li>NF4 offers the best compression with acceptable accuracy retention</li><li>AWQ often provides better accuracy than uniform quantization methods</li></ul><h2 id="Best-Practices-and-Pitfalls"><a href="#Best-Practices-and-Pitfalls" class="headerlink" title="Best Practices and Pitfalls"></a>Best Practices and Pitfalls</h2><h3 id="Do’s"><a href="#Do’s" class="headerlink" title="Do’s"></a>Do’s</h3><ul><li><strong>Start with distillation before quantization</strong>: A distilled model generalizes better and is more robust to quantization</li><li><strong>Use calibration data</strong>: Always calibrate quantization on representative data</li><li><strong>Monitor per-layer sensitivity</strong>: Not all layers are equally sensitive to quantization</li><li><strong>Test on target hardware</strong>: Benchmarks vary significantly across different edge devices</li><li><strong>Maintain a fallback</strong>: Keep the FP16 model available for critical tasks</li></ul><h3 id="Don’ts"><a href="#Don’ts" class="headerlink" title="Don’ts"></a>Don’ts</h3><ul><li><strong>Don’t quantize without validation</strong>: Always compare outputs against the original model</li><li><strong>Don’t ignore attention mechanisms</strong>: Self-attention layers are often more sensitive to precision loss</li><li><strong>Don’t use aggressive quantization blindly</strong>: Start with INT8, then move to INT4&#x2F;NF4 if needed</li><li><strong>Don’t forget about activation quantization</strong>: Weight-only quantization is easier but less effective</li><li><strong>Don’t skip the evaluation</strong>: Use multiple benchmarks (MMLU, HumanEval, TruthfulQA)</li></ul><h2 id="Tools-and-Libraries"><a href="#Tools-and-Libraries" class="headerlink" title="Tools and Libraries"></a>Tools and Libraries</h2><h3 id="Recommended-Stack"><a href="#Recommended-Stack" class="headerlink" title="Recommended Stack"></a>Recommended Stack</h3><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">distillation:</span></span><br><span class="line">  <span class="attr">libraries:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">peft:</span> <span class="string">Low-rank</span> <span class="string">adaptation</span> <span class="string">for</span> <span class="string">efficient</span> <span class="string">fine-tuning</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">transformers:</span> <span class="string">Hugging</span> <span class="string">Face</span> <span class="string">transformers</span> <span class="string">library</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">trl:</span> <span class="string">Transformers</span> <span class="string">Reinforcement</span> <span class="string">Learning</span></span><br><span class="line">    </span><br><span class="line"><span class="attr">quantization:</span></span><br><span class="line">  <span class="attr">libraries:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">bitsandbytes:</span> <span class="number">4</span><span class="string">-bit</span> <span class="string">and</span> <span class="number">8</span><span class="string">-bit</span> <span class="string">quantization</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">awq:</span> <span class="string">Activation-aware</span> <span class="string">weight</span> <span class="string">quantization</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">llama.cpp:</span> <span class="string">GGUF</span> <span class="string">format</span> <span class="string">with</span> <span class="string">multiple</span> <span class="string">quantization</span> <span class="string">options</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">onnxruntime:</span> <span class="string">Cross-platform</span> <span class="string">inference</span> <span class="string">with</span> <span class="string">quantization</span></span><br><span class="line">    </span><br><span class="line"><span class="attr">deployment:</span></span><br><span class="line">  <span class="attr">libraries:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">vLLM:</span> <span class="string">High-throughput</span> <span class="string">serving</span> <span class="string">with</span> <span class="string">PagedAttention</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">tensorrt-llm:</span> <span class="string">NVIDIA’s</span> <span class="string">optimized</span> <span class="string">inference</span> <span class="string">engine</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">coreml:</span> <span class="string">Apple’s</span> <span class="string">edge</span> <span class="string">ML</span> <span class="string">framework</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">openvino:</span> <span class="string">Intel’s</span> <span class="string">optimization</span> <span class="string">toolkit</span></span><br></pre></td></tr></table></figure><h3 id="Complete-Pipeline-Example"><a href="#Complete-Pipeline-Example" class="headerlink" title="Complete Pipeline Example"></a>Complete Pipeline Example</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#!/usr/bin/env python3</span></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">Complete LLM distillation and quantization pipeline</span></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> torch</span><br><span class="line"><span class="keyword">from</span> transformers <span class="keyword">import</span> AutoModelForCausalLM, AutoTokenizer</span><br><span class="line"><span class="keyword">from</span> peft <span class="keyword">import</span> LoraConfig, get_peft_model</span><br><span class="line"><span class="keyword">from</span> transformers <span class="keyword">import</span> BitsAndBytesConfig</span><br><span class="line"><span class="keyword">from</span> datasets <span class="keyword">import</span> Dataset</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">LLMDistillQuantPipeline</span>:</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, teacher_model, student_model, output_dir</span>):</span><br><span class="line">        <span class="variable language_">self</span>.teacher = AutoModelForCausalLM.from_pretrained(</span><br><span class="line">            teacher_model, torch_dtype=torch.float16, device_map=<span class="string">&quot;auto&quot;</span></span><br><span class="line">        )</span><br><span class="line">        <span class="variable language_">self</span>.student = AutoModelForCausalLM.from_pretrained(</span><br><span class="line">            student_model, torch_dtype=torch.float16, device_map=<span class="string">&quot;auto&quot;</span></span><br><span class="line">        )</span><br><span class="line">        <span class="variable language_">self</span>.tokenizer = AutoTokenizer.from_pretrained(student_model)</span><br><span class="line">        <span class="variable language_">self</span>.output_dir = output_dir</span><br><span class="line">        </span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">distill</span>(<span class="params">self, training_data, epochs=<span class="number">3</span>, learning_rate=<span class="number">2e-4</span></span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;Perform knowledge distillation using LoRA&quot;&quot;&quot;</span></span><br><span class="line">        <span class="comment"># Apply LoRA</span></span><br><span class="line">        lora_config = LoraConfig(r=<span class="number">16</span>, lora_alpha=<span class="number">32</span>, task_type=<span class="string">&quot;CAUSAL_LM&quot;</span>)</span><br><span class="line">        <span class="variable language_">self</span>.student = get_peft_model(<span class="variable language_">self</span>.student, lora_config)</span><br><span class="line">        </span><br><span class="line">        <span class="comment"># Convert data to Dataset</span></span><br><span class="line">        dataset = Dataset.from_list(training_data)</span><br><span class="line">        </span><br><span class="line">        <span class="comment"># Training loop (simplified)</span></span><br><span class="line">        <span class="built_in">print</span>(<span class="string">f&quot;Distilling <span class="subst">&#123;<span class="built_in">len</span>(dataset)&#125;</span> samples...&quot;</span>)</span><br><span class="line">        <span class="comment"># ... training implementation ...</span></span><br><span class="line">        </span><br><span class="line">        <span class="comment"># Merge LoRA weights</span></span><br><span class="line">        <span class="variable language_">self</span>.student = <span class="variable language_">self</span>.student.merge_and_unload()</span><br><span class="line">        </span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">quantize</span>(<span class="params">self, quant_type=<span class="string">&quot;nf4&quot;</span></span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;Apply quantization to distilled model&quot;&quot;&quot;</span></span><br><span class="line">        quant_config = BitsAndBytesConfig(</span><br><span class="line">            load_in_4bit=<span class="literal">True</span>,</span><br><span class="line">            bnb_4bit_quant_type=quant_type,</span><br><span class="line">            bnb_4bit_compute_dtype=torch.float16,</span><br><span class="line">            bnb_4bit_use_double_quant=<span class="literal">True</span></span><br><span class="line">        )</span><br><span class="line">        </span><br><span class="line">        <span class="variable language_">self</span>.quantized_model = AutoModelForCausalLM.from_pretrained(</span><br><span class="line">            <span class="variable language_">self</span>.output_dir,</span><br><span class="line">            quantization_config=quant_config,</span><br><span class="line">            device_map=<span class="string">&quot;auto&quot;</span></span><br><span class="line">        )</span><br><span class="line">        </span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">save</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;Save quantized model&quot;&quot;&quot;</span></span><br><span class="line">        <span class="variable language_">self</span>.quantized_model.save_pretrained(<span class="variable language_">self</span>.output_dir)</span><br><span class="line">        <span class="variable language_">self</span>.tokenizer.save_pretrained(<span class="variable language_">self</span>.output_dir)</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">f&quot;Model saved to <span class="subst">&#123;self.output_dir&#125;</span>&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># Usage</span></span><br><span class="line">pipeline = LLMDistillQuantPipeline(</span><br><span class="line">    teacher_model=<span class="string">&quot;mistralai/Mistral-7B-v0.1&quot;</span>,</span><br><span class="line">    student_model=<span class="string">&quot;mistralai/Mistral-7B-v0.1&quot;</span>,</span><br><span class="line">    output_dir=<span class="string">&quot;./distilled-quantized-model&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment"># Run pipeline</span></span><br><span class="line">pipeline.distill(training_data=synthetic_data)</span><br><span class="line">pipeline.quantize(quant_type=<span class="string">&quot;nf4&quot;</span>)</span><br><span class="line">pipeline.save()</span><br></pre></td></tr></table></figure><h2 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h2><p>Distilling and quantizing LLMs for edge and on-prem deployment is no longer a research curiosity—it’s a practical necessity for bringing AI to resource-constrained environments. By combining knowledge distillation with advanced quantization techniques like NF4 and AWQ, you can achieve 4-8x compression while maintaining 95%+ of the original model’s performance.</p><p>The key insights from this guide:</p><ul><li><strong>Distillation first, quantization second</strong>: Train a smaller model with teacher guidance before applying aggressive quantization</li><li><strong>LoRA makes distillation efficient</strong>: Low-rank adaptation reduces training costs by 99% while preserving knowledge</li><li><strong>NF4 and AWQ are game-changers</strong>: These specialized quantization methods offer better accuracy than traditional INT4</li><li><strong>Hardware-aware deployment</strong>: Match your quantization strategy to your target hardware’s capabilities</li><li><strong>Validation is critical</strong>: Always benchmark quantized models against multiple evaluation metrics</li></ul><p>As edge AI continues to mature, these techniques will become standard practice for any production LLM deployment. The trade-off between model size, speed, and accuracy is no longer a constraint—it’s a design choice you can optimize for your specific use case.</p><h2 id="Key-Takeaways"><a href="#Key-Takeaways" class="headerlink" title="Key Takeaways"></a>Key Takeaways</h2><ol><li><p><strong>Model distillation transfers knowledge</strong> from large teacher models to smaller students using KL divergence on softened logits, with LoRA enabling efficient fine-tuning at 0.1% trainable parameters</p></li><li><p><strong>Quantization reduces memory and computation</strong> through precision reduction: INT8 provides 2x compression, NF4 achieves 4.5x with minimal accuracy loss, and AWQ preserves important weights for better quality retention</p></li><li><p><strong>The optimal pipeline combines both techniques</strong>: distill first to create a smaller, knowledge-rich model, then quantize for deployment, achieving 4-8x compression while maintaining 95%+ of original performance</p></li><li><p><strong>Edge deployment requires hardware-aware optimization</strong>: Match quantization choices to target devices—NF4 for severe memory constraints, INT8 for balanced performance, and consider ONNX Runtime for maximum portability</p></li><li><p><strong>Validation and calibration are essential</strong>: Always test quantized models on representative data using multiple benchmarks (MMLU, HumanEval, TruthfulQA), and use calibration datasets to minimize accuracy degradation</p></li><li><p><strong>Production-ready tools exist</strong>: Leverage libraries like PEFT for distillation, BitsAndBytes&#x2F;AWQ for quantization, and vLLM&#x2F;TensorRT-LLM for high-throughput serving to streamline the deployment pipeline</p></li></ol>]]>
    </content>
    <id>https://thoughtfly.github.io/devtech/2026/09/02/distilling-and-quantizing-llms-for-edge-and-on-prem-deployment/</id>
    <link href="https://thoughtfly.github.io/devtech/2026/09/02/distilling-and-quantizing-llms-for-edge-and-on-prem-deployment/"/>
    <published>2026-09-02T16:00:00.000Z</published>
    <summary>Learn how to distill and quantize large language models for efficient edge and on-prem deployment, reducing latency and costs while maintaining accuracy.</summary>
    <title>Distilling and Quantizing LLMs for Edge and On-Prem Deployment</title>
    <updated>2026-09-21T14:46:52.850Z</updated>
  </entry>
  <entry>
    <author>
      <name>DevTech Team</name>
    </author>
    <category term="Java" scheme="https://thoughtfly.github.io/devtech/categories/Java/"/>
    <category term="AI/ML" scheme="https://thoughtfly.github.io/devtech/categories/Java/AI-ML/"/>
    <category term="System Design" scheme="https://thoughtfly.github.io/devtech/categories/Java/AI-ML/System-Design/"/>
    <category term="Small Language Models" scheme="https://thoughtfly.github.io/devtech/tags/Small-Language-Models/"/>
    <category term="Phi" scheme="https://thoughtfly.github.io/devtech/tags/Phi/"/>
    <category term="Gemma" scheme="https://thoughtfly.github.io/devtech/tags/Gemma/"/>
    <category term="MiniCPM" scheme="https://thoughtfly.github.io/devtech/tags/MiniCPM/"/>
    <category term="Edge AI" scheme="https://thoughtfly.github.io/devtech/tags/Edge-AI/"/>
    <category term="Java AI" scheme="https://thoughtfly.github.io/devtech/tags/Java-AI/"/>
    <category term="LLM Optimization" scheme="https://thoughtfly.github.io/devtech/tags/LLM-Optimization/"/>
    <content>
      <![CDATA[<h2 id="The-Shift-from-Giant-LLMs-to-Practical-SLMs"><a href="#The-Shift-from-Giant-LLMs-to-Practical-SLMs" class="headerlink" title="The Shift from Giant LLMs to Practical SLMs"></a>The Shift from Giant LLMs to Practical SLMs</h2><p>For the past two years, the AI narrative has been dominated by parameter counts. We watched models balloon from billions to trillions of parameters, with cloud APIs offering increasingly capable but expensive and latency-heavy solutions. But as engineers, we know that not every problem requires a sledgehammer. Sometimes, you just need a precision screwdriver.</p><p>This is where Small Language Models (SLMs) enter the chatroom. With the rise of Phi, Gemma, and MiniCPM, we are seeing a fundamental shift in how we approach AI integration in production systems. These models are not just “smaller versions” of their larger cousins—they are architecturally distinct, optimized for efficiency, and designed for specific deployment contexts where latency, cost, and privacy matter more than raw capability.</p><p>In this post, we will dive deep into three of the most promising SLMs: Microsoft Phi, Google Gemma, and the MiniCPM family. We will explore when to use each one, how to deploy them in Java applications, and the practical trade-offs you need to consider.</p><h2 id="What-Exactly-is-a-Small-Language-Model"><a href="#What-Exactly-is-a-Small-Language-Model" class="headerlink" title="What Exactly is a Small Language Model?"></a>What Exactly is a Small Language Model?</h2><p>Before we compare specific models, let us clarify what we mean by “small.” In the LLM world, this typically refers to models with between 1 billion and 13 billion parameters. While this sounds modest compared to the 70B+ models dominating headlines, recent research has shown that these smaller models can achieve surprising performance when trained on high-quality, curated datasets.</p><p>The key advantages of SLMs include:</p><ul><li><strong>Deployment Flexibility</strong>: Run on consumer hardware, edge devices, or within containerized microservices without requiring massive GPU clusters.</li><li><strong>Latency Control</strong>: Sub-second response times for many inference tasks, critical for real-time applications.</li><li><strong>Cost Efficiency</strong>: Dramatically lower inference costs, whether you are self-hosting or using cloud GPU instances.</li><li><strong>Privacy and Compliance</strong>: Data can remain on-premises, satisfying GDPR and other regulatory requirements without sending sensitive information to third-party APIs.</li></ul><h2 id="Microsoft-Phi-The-Power-of-Synthetic-Data"><a href="#Microsoft-Phi-The-Power-of-Synthetic-Data" class="headerlink" title="Microsoft Phi: The Power of Synthetic Data"></a>Microsoft Phi: The Power of Synthetic Data</h2><p>Microsoft Phi models represent a paradigm shift in how we think about model size. Traditional wisdom suggested that larger datasets and more parameters were the only path to capability. Phi challenged this by demonstrating that high-quality synthetic data could train smaller models to perform competitively with much larger ones.</p><h3 id="Why-Phi-Stands-Out"><a href="#Why-Phi-Stands-Out" class="headerlink" title="Why Phi Stands Out"></a>Why Phi Stands Out</h3><p>Phi-2, with just 2.7 billion parameters, was a revelation. It demonstrated that careful data curation—using synthetic data generated from larger models—could produce models that punch well above their weight class. The newer Phi-3 series, including the Phi-3-mini (3.8B) and Phi-3-small (7B) variants, has taken this further, offering competitive performance on coding and reasoning benchmarks.</p><p>The Phi models excel in:</p><ul><li><strong>Code Generation</strong>: Phi-3-mini often outperforms models twice its size on HumanEval and other coding benchmarks.</li><li><strong>Reasoning Tasks</strong>: Strong performance on math and logic puzzles, thanks to the synthetic data training approach.</li><li><strong>Multilingual Support</strong>: Recent versions support multiple languages, making them suitable for global applications.</li></ul><h3 id="When-to-Choose-Phi"><a href="#When-to-Choose-Phi" class="headerlink" title="When to Choose Phi"></a>When to Choose Phi</h3><p>Choose Phi when your application involves:</p><ul><li>Code generation or completion tasks</li><li>Reasoning-heavy workflows where accuracy matters more than creative writing</li><li>Scenarios where you need to balance performance with resource constraints</li><li>Applications requiring good multilingual support</li></ul><h2 id="Google-Gemma-Open-and-Efficient"><a href="#Google-Gemma-Open-and-Efficient" class="headerlink" title="Google Gemma: Open and Efficient"></a>Google Gemma: Open and Efficient</h2><p>Google Gemma models are built on the same technology as Google Gemini but are distilled into smaller, open-weight packages. This approach gives developers access to Google-grade capabilities without the black-box nature of proprietary APIs.</p><h3 id="The-Gemma-Architecture"><a href="#The-Gemma-Architecture" class="headerlink" title="The Gemma Architecture"></a>The Gemma Architecture</h3><p>Gemma comes in two primary sizes: Gemma 2B and Gemma 7B (with the newer Gemma 2 offering 9B and 27B variants). These models are designed to be efficient while maintaining strong performance across a variety of tasks.</p><p>Key characteristics of Gemma:</p><ul><li><strong>Open Weights</strong>: Fully open for commercial use, making them ideal for production deployments.</li><li><strong>Efficient Architecture</strong>: Built with modern attention mechanisms and optimized for inference speed.</li><li><strong>Versatile Capabilities</strong>: Strong performance across chat, reasoning, and general language tasks.</li><li><strong>Fine-tuning Friendly</strong>: Well-documented and supported by frameworks like Hugging Face Transformers and LangChain.</li></ul><h3 id="When-to-Choose-Gemma"><a href="#When-to-Choose-Gemma" class="headerlink" title="When to Choose Gemma"></a>When to Choose Gemma</h3><p>Gemma is an excellent choice when:</p><ul><li>You need a general-purpose model for chat or conversational applications</li><li>Commercial usage rights are important for your project</li><li>You want a model that balances capability with reasonable resource requirements</li><li>Your team is already familiar with the Hugging Face ecosystem</li></ul><h2 id="MiniCPM-Edge-Ready-Performance"><a href="#MiniCPM-Edge-Ready-Performance" class="headerlink" title="MiniCPM: Edge-Ready Performance"></a>MiniCPM: Edge-Ready Performance</h2><p>MiniCPM (Mini Common Multimodal Model) represents a different philosophy: maximizing performance per parameter through efficient design and multimodal capabilities. Developed by a team at Tsinghua University and Moonshot AI, MiniCPM models have gained attention for their ability to run on edge devices while maintaining competitive performance.</p><h3 id="The-MiniCPM-Advantage"><a href="#The-MiniCPM-Advantage" class="headerlink" title="The MiniCPM Advantage"></a>The MiniCPM Advantage</h3><p>MiniCPM models, particularly the 2B and 8B variants, are designed with edge deployment in mind. They feature:</p><ul><li><strong>Efficient Attention Mechanisms</strong>: Optimized for low-latency inference on constrained hardware.</li><li><strong>Multimodal Support</strong>: Some variants support image understanding, making them suitable for applications requiring vision-language capabilities.</li><li><strong>Strong Multilingual Performance</strong>: Excellent support for both English and Chinese, with good performance across other languages.</li><li><strong>Resource Efficiency</strong>: Designed to run on devices with as little as 4GB of RAM.</li></ul><h3 id="When-to-Choose-MiniCPM"><a href="#When-to-Choose-MiniCPM" class="headerlink" title="When to Choose MiniCPM"></a>When to Choose MiniCPM</h3><p>MiniCPM is ideal for:</p><ul><li>Edge deployments on mobile devices, IoT sensors, or embedded systems</li><li>Applications requiring multimodal capabilities (text + images)</li><li>Scenarios where Chinese language support is important</li><li>Resource-constrained environments where every megabyte counts</li></ul><h2 id="Practical-Deployment-in-Java-Applications"><a href="#Practical-Deployment-in-Java-Applications" class="headerlink" title="Practical Deployment in Java Applications"></a>Practical Deployment in Java Applications</h2><p>Now that we understand the strengths of each model, let us look at how to actually deploy them in Java applications. The Java ecosystem has matured significantly for AI workloads, with several excellent options available.</p><h3 id="Using-Ollama-with-Java"><a href="#Using-Ollama-with-Java" class="headerlink" title="Using Ollama with Java"></a>Using Ollama with Java</h3><p>Ollama provides a simple way to run local LLMs, and it integrates well with Java applications through HTTP APIs. Here is how you can set up a basic integration:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.net.URI;</span><br><span class="line"><span class="keyword">import</span> java.net.http.HttpClient;</span><br><span class="line"><span class="keyword">import</span> java.net.http.HttpRequest;</span><br><span class="line"><span class="keyword">import</span> java.net.http.HttpResponse;</span><br><span class="line"><span class="keyword">import</span> com.google.gson.JsonObject;</span><br><span class="line"><span class="keyword">import</span> com.google.gson.JsonParser;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SLMClient</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">OLLAMA_HOST</span> <span class="operator">=</span> <span class="string">&quot;http://localhost:11434&quot;</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">generate</span><span class="params">(String model, String prompt)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="type">HttpClient</span> <span class="variable">client</span> <span class="operator">=</span> HttpClient.newHttpClient();</span><br><span class="line">        </span><br><span class="line">        <span class="type">JsonObject</span> <span class="variable">requestBody</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">JsonObject</span>();</span><br><span class="line">        requestBody.addProperty(<span class="string">&quot;model&quot;</span>, model);</span><br><span class="line">        requestBody.addProperty(<span class="string">&quot;prompt&quot;</span>, prompt);</span><br><span class="line">        requestBody.addProperty(<span class="string">&quot;stream&quot;</span>, <span class="literal">false</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="type">HttpRequest</span> <span class="variable">request</span> <span class="operator">=</span> HttpRequest.newBuilder()</span><br><span class="line">            .uri(URI.create(OLLAMA_HOST + <span class="string">&quot;/api/generate&quot;</span>))</span><br><span class="line">            .header(<span class="string">&quot;Content-Type&quot;</span>, <span class="string">&quot;application/json&quot;</span>)</span><br><span class="line">            .POST(HttpRequest.BodyPublishers.ofString(requestBody.toString()))</span><br><span class="line">            .build();</span><br><span class="line">        </span><br><span class="line">        HttpResponse&lt;String&gt; response = client.send(request, HttpResponse.BodyHandlers.ofString());</span><br><span class="line">        <span class="type">JsonObject</span> <span class="variable">jsonResponse</span> <span class="operator">=</span> JsonParser.parseString(response.body()).getAsJsonObject();</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> jsonResponse.get(<span class="string">&quot;response&quot;</span>).getAsString();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Using-LangChain4j-for-Advanced-Workflows"><a href="#Using-LangChain4j-for-Advanced-Workflows" class="headerlink" title="Using LangChain4j for Advanced Workflows"></a>Using LangChain4j for Advanced Workflows</h3><p>For more sophisticated applications, LangChain4j provides a robust framework for building AI-powered Java applications. Here is how you can integrate Phi-3 with LangChain4j:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> dev.langchain4j.model.ollama.OllamaChatModel;</span><br><span class="line"><span class="keyword">import</span> dev.langchain4j.service.AiServices;</span><br><span class="line"><span class="keyword">import</span> dev.langchain4j.service.SystemMessage;</span><br><span class="line"><span class="keyword">import</span> dev.langchain4j.service.UserMessage;</span><br><span class="line"><span class="keyword">import</span> dev.langchain4j.service.MemoryId;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">Assistant</span> &#123;</span><br><span class="line">    <span class="meta">@SystemMessage(&quot;You are a helpful assistant specialized in code review.&quot;)</span></span><br><span class="line">    String <span class="title function_">chat</span><span class="params">(<span class="meta">@MemoryId</span> <span class="type">long</span> memoryId, <span class="meta">@UserMessage</span> String userMessage)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Setup</span></span><br><span class="line"><span class="type">OllamaChatModel</span> <span class="variable">model</span> <span class="operator">=</span> OllamaChatModel.builder()</span><br><span class="line">    .baseUrl(<span class="string">&quot;http://localhost:11434&quot;</span>)</span><br><span class="line">    .modelName(<span class="string">&quot;phi3&quot;</span>)</span><br><span class="line">    .temperature(<span class="number">0.7</span>)</span><br><span class="line">    .build();</span><br><span class="line"></span><br><span class="line"><span class="type">Assistant</span> <span class="variable">assistant</span> <span class="operator">=</span> AiServices.builder(Assistant.class)</span><br><span class="line">    .chatLanguageModel(model)</span><br><span class="line">    .build();</span><br><span class="line"></span><br><span class="line"><span class="comment">// Usage</span></span><br><span class="line"><span class="type">String</span> <span class="variable">response</span> <span class="operator">=</span> assistant.chat(<span class="number">1L</span>, <span class="string">&quot;Review this Java code for potential bugs:&quot;</span>);</span><br></pre></td></tr></table></figure><h3 id="Docker-Deployment-Considerations"><a href="#Docker-Deployment-Considerations" class="headerlink" title="Docker Deployment Considerations"></a>Docker Deployment Considerations</h3><p>When deploying SLMs in production, Docker containers provide excellent isolation and reproducibility. Here is a sample Dockerfile for running Ollama with Phi-3:</p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">FROM</span> ollama/ollama:latest</span><br><span class="line"></span><br><span class="line"><span class="comment"># Pull the Phi-3 model during build</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> ollama pull phi3</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Expose the Ollama API port</span></span><br><span class="line"><span class="keyword">EXPOSE</span> <span class="number">11434</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Run Ollama in the background</span></span><br><span class="line"><span class="keyword">CMD</span><span class="language-bash"> [<span class="string">&quot;ollama&quot;</span>, <span class="string">&quot;serve&quot;</span>]</span></span><br></pre></td></tr></table></figure><p>And the corresponding docker-compose.yml for a complete stack:</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">&#x27;3.8&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">ollama:</span></span><br><span class="line">    <span class="attr">build:</span> <span class="string">.</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;11434:11434&quot;</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">ollama_models:/root/.ollama</span></span><br><span class="line">    <span class="attr">restart:</span> <span class="string">unless-stopped</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">app:</span></span><br><span class="line">    <span class="attr">build:</span> <span class="string">./java-app</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;8080:8080&quot;</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">OLLAMA_HOST=http://ollama:11434</span></span><br><span class="line">    <span class="attr">depends_on:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">ollama</span></span><br><span class="line">    <span class="attr">restart:</span> <span class="string">unless-stopped</span></span><br><span class="line"></span><br><span class="line"><span class="attr">volumes:</span></span><br><span class="line">  <span class="attr">ollama_models:</span></span><br></pre></td></tr></table></figure><h2 id="Performance-Benchmarks-and-Trade-offs"><a href="#Performance-Benchmarks-and-Trade-offs" class="headerlink" title="Performance Benchmarks and Trade-offs"></a>Performance Benchmarks and Trade-offs</h2><p>Understanding the performance characteristics of each model is crucial for making the right choice. While exact benchmarks vary based on hardware and implementation, here are some general observations from production deployments.</p><h3 id="Inference-Speed-Comparison"><a href="#Inference-Speed-Comparison" class="headerlink" title="Inference Speed Comparison"></a>Inference Speed Comparison</h3><p>On a typical consumer GPU (NVIDIA RTX 4090):</p><ul><li><strong>Phi-3-mini (3.8B)</strong>: ~50-80 tokens&#x2F;second</li><li><strong>Gemma-2B</strong>: ~60-100 tokens&#x2F;second</li><li><strong>MiniCPM-2B</strong>: ~70-110 tokens&#x2F;second</li></ul><p>On CPU-only deployment (modern laptop):</p><ul><li><strong>Phi-3-mini</strong>: ~5-15 tokens&#x2F;second</li><li><strong>Gemma-2B</strong>: ~8-20 tokens&#x2F;second</li><li><strong>MiniCPM-2B</strong>: ~10-25 tokens&#x2F;second</li></ul><h3 id="Memory-Requirements"><a href="#Memory-Requirements" class="headerlink" title="Memory Requirements"></a>Memory Requirements</h3><ul><li><strong>Phi-3-mini</strong>: ~8GB RAM for inference (can run with 4GB using quantization)</li><li><strong>Gemma-2B</strong>: ~6GB RAM for inference</li><li><strong>MiniCPM-2B</strong>: ~5GB RAM for inference</li></ul><h3 id="Quality-Trade-offs"><a href="#Quality-Trade-offs" class="headerlink" title="Quality Trade-offs"></a>Quality Trade-offs</h3><p>It is important to manage expectations when using SLMs. While they have made remarkable progress, they still lag behind larger models in:</p><ul><li><strong>Complex Reasoning</strong>: Multi-step reasoning tasks may produce errors</li><li><strong>Creative Writing</strong>: Less nuanced and creative output compared to larger models</li><li><strong>Factual Accuracy</strong>: Higher likelihood of hallucinations, especially on niche topics</li><li><strong>Context Length</strong>: Most SLMs support shorter contexts (4K-8K tokens) compared to larger models (32K+ tokens)</li></ul><h2 id="Making-the-Right-Choice"><a href="#Making-the-Right-Choice" class="headerlink" title="Making the Right Choice"></a>Making the Right Choice</h2><p>Choosing between Phi, Gemma, and MiniCPM depends on your specific requirements. Here is a decision framework:</p><h3 id="Choose-Phi-when"><a href="#Choose-Phi-when" class="headerlink" title="Choose Phi when:"></a>Choose Phi when:</h3><ul><li>Your primary use case involves coding or technical reasoning</li><li>You need strong multilingual support</li><li>You are willing to trade some creative capability for reasoning performance</li><li>You want a model with excellent documentation and community support</li></ul><h3 id="Choose-Gemma-when"><a href="#Choose-Gemma-when" class="headerlink" title="Choose Gemma when:"></a>Choose Gemma when:</h3><ul><li>You need a general-purpose chat model</li><li>Commercial usage rights are important</li><li>Your team is already using the Hugging Face ecosystem</li><li>You want a balance between capability and resource usage</li></ul><h3 id="Choose-MiniCPM-when"><a href="#Choose-MiniCPM-when" class="headerlink" title="Choose MiniCPM when:"></a>Choose MiniCPM when:</h3><ul><li>You are deploying to edge devices or resource-constrained environments</li><li>You need multimodal capabilities (text + images)</li><li>Chinese language support is important for your application</li><li>Every megabyte of memory counts</li></ul><h2 id="Production-Best-Practices"><a href="#Production-Best-Practices" class="headerlink" title="Production Best Practices"></a>Production Best Practices</h2><p>Regardless of which model you choose, these best practices will help ensure success in production:</p><h3 id="1-Implement-Proper-Error-Handling"><a href="#1-Implement-Proper-Error-Handling" class="headerlink" title="1. Implement Proper Error Handling"></a>1. Implement Proper Error Handling</h3><p>SLMs can produce unexpected outputs or fail gracefully. Always implement robust error handling:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SLMService</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">MAX_RETRIES</span> <span class="operator">=</span> <span class="number">3</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Duration</span> <span class="variable">TIMEOUT</span> <span class="operator">=</span> Duration.ofSeconds(<span class="number">30</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">generateWithRetry</span><span class="params">(String model, String prompt)</span> &#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; MAX_RETRIES; i++) &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="type">String</span> <span class="variable">result</span> <span class="operator">=</span> generate(model, prompt);</span><br><span class="line">                <span class="keyword">if</span> (isValidResponse(result)) &#123;</span><br><span class="line">                    <span class="keyword">return</span> result;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">                <span class="keyword">if</span> (i == MAX_RETRIES - <span class="number">1</span>) &#123;</span><br><span class="line">                    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(<span class="string">&quot;Failed to generate response&quot;</span>, e);</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    Thread.sleep(Duration.ofSeconds(<span class="number">1</span>).toMillis() * (i + <span class="number">1</span>));</span><br><span class="line">                &#125; <span class="keyword">catch</span> (InterruptedException ie) &#123;</span><br><span class="line">                    Thread.currentThread().interrupt();</span><br><span class="line">                    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(<span class="string">&quot;Interrupted during retry&quot;</span>, ie);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-Monitor-and-Log-Performance"><a href="#2-Monitor-and-Log-Performance" class="headerlink" title="2. Monitor and Log Performance"></a>2. Monitor and Log Performance</h3><p>Track key metrics to identify issues early:</p><ul><li>Response latency</li><li>Token generation rate</li><li>Error rates</li><li>Memory and CPU usage</li><li>User satisfaction metrics</li></ul><h3 id="3-Implement-Caching-Strategies"><a href="#3-Implement-Caching-Strategies" class="headerlink" title="3. Implement Caching Strategies"></a>3. Implement Caching Strategies</h3><p>For repeated queries, implement caching to reduce latency and costs:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> com.google.common.cache.Cache;</span><br><span class="line"><span class="keyword">import</span> com.google.common.cache.CacheBuilder;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.TimeUnit;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SLMCache</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Cache&lt;String, String&gt; responseCache = CacheBuilder.newBuilder()</span><br><span class="line">        .maximumSize(<span class="number">1000</span>)</span><br><span class="line">        .expireAfterWrite(<span class="number">10</span>, TimeUnit.MINUTES)</span><br><span class="line">        .build();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getCachedOrGenerate</span><span class="params">(String model, String prompt, Supplier&lt;String&gt; generator)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">cacheKey</span> <span class="operator">=</span> model + <span class="string">&quot;:&quot;</span> + prompt.hashCode();</span><br><span class="line">        <span class="keyword">return</span> responseCache.get(cacheKey, () -&gt; generator.get());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-Use-Quantization-for-Resource-Optimization"><a href="#4-Use-Quantization-for-Resource-Optimization" class="headerlink" title="4. Use Quantization for Resource Optimization"></a>4. Use Quantization for Resource Optimization</h3><p>Quantization can significantly reduce memory usage with minimal quality loss:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Using Ollama&#x27;s built-in quantization</span></span><br><span class="line">ollama pull phi3:q4_0</span><br><span class="line"></span><br><span class="line"><span class="comment"># Or using llama.cpp for more control</span></span><br><span class="line">./quantize model.bin q4_0.bin q4_0</span><br></pre></td></tr></table></figure><h3 id="5-Implement-Fallback-Mechanisms"><a href="#5-Implement-Fallback-Mechanisms" class="headerlink" title="5. Implement Fallback Mechanisms"></a>5. Implement Fallback Mechanisms</h3><p>Always have a fallback strategy in case your SLM fails or produces poor quality output:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ResilientSLMService</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> SLMClient primaryClient;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> SLMClient fallbackClient;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">generate</span><span class="params">(String prompt)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> primaryClient.generate(prompt);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            log.warn(<span class="string">&quot;Primary model failed, falling back to secondary&quot;</span>, e);</span><br><span class="line">            <span class="keyword">return</span> fallbackClient.generate(prompt);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="The-Future-of-Small-Language-Models"><a href="#The-Future-of-Small-Language-Models" class="headerlink" title="The Future of Small Language Models"></a>The Future of Small Language Models</h2><p>The SLM landscape is evolving rapidly. We are seeing:</p><ul><li><strong>Increasing Capabilities</strong>: Each new generation of SLMs closes the gap with larger models</li><li><strong>Better Tooling</strong>: Improved frameworks and deployment options make SLMs easier to use</li><li><strong>Specialized Models</strong>: More models optimized for specific tasks (coding, math, multilingual)</li><li><strong>Edge Optimization</strong>: Continued improvements in running models on mobile and embedded devices</li></ul><p>As these trends continue, SLMs will become increasingly viable for a wider range of production applications. The key is understanding their strengths and limitations, and choosing the right model for your specific use case.</p><h2 id="Key-Takeaways"><a href="#Key-Takeaways" class="headerlink" title="Key Takeaways"></a>Key Takeaways</h2><ul><li><strong>SLMs are production-ready</strong>: Models like Phi, Gemma, and MiniCPM offer viable alternatives to large cloud-based LLMs for many use cases.</li><li><strong>Choose based on requirements</strong>: Phi excels at reasoning and coding, Gemma offers balanced general-purpose capabilities, and MiniCPM is ideal for edge and multimodal applications.</li><li><strong>Java ecosystem is mature</strong>: Tools like Ollama, LangChain4j, and Docker make it straightforward to deploy SLMs in Java applications.</li><li><strong>Performance trade-offs exist</strong>: SLMs sacrifice some capability for efficiency, but the gap is narrowing rapidly.</li><li><strong>Production considerations matter</strong>: Implement proper error handling, monitoring, caching, and fallback mechanisms for reliable deployments.</li><li><strong>Start small and iterate</strong>: Begin with a 2B-7B parameter model and scale up only if necessary. The best model is often the smallest one that gets the job done.</li></ul><p>The future of AI in production is not just about bigger models—it is about using the right tool for the job. Small Language Models are proving that you do not always need a sledgehammer when a precision instrument will do.</p>]]>
    </content>
    <id>https://thoughtfly.github.io/devtech/2026/09/01/small-language-models-slms-when-to-use-phi-gemma-and-minicpm/</id>
    <link href="https://thoughtfly.github.io/devtech/2026/09/01/small-language-models-slms-when-to-use-phi-gemma-and-minicpm/"/>
    <published>2026-09-01T16:00:00.000Z</published>
    <summary>A practical guide to deploying Phi, Gemma, and MiniCPM on edge devices and in Java applications. Compare capabilities, latency, and resource usage for produc...</summary>
    <title>Small Language Models (SLMs): When to Use Phi, Gemma, and MiniCPM</title>
    <updated>2026-09-21T14:46:52.850Z</updated>
  </entry>
</feed>
