<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ayush Shrivastava</title>
    <description>The latest articles on DEV Community by Ayush Shrivastava (@ayshriv).</description>
    <link>https://dev.to/ayshriv</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1478693%2Fb6eb15b2-ff8c-4d2d-9fe8-015763db0976.jpg</url>
      <title>DEV Community: Ayush Shrivastava</title>
      <link>https://dev.to/ayshriv</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ayshriv"/>
    <language>en</language>
    <item>
      <title>Model Context Protocol with Spring AI, Building MCP Clients and Servers in Java</title>
      <dc:creator>Ayush Shrivastava</dc:creator>
      <pubDate>Sat, 19 Sep 2026 07:37:01 +0000</pubDate>
      <link>https://dev.to/ayshriv/model-context-protocol-with-spring-ai-building-mcp-clients-and-servers-in-java-2146</link>
      <guid>https://dev.to/ayshriv/model-context-protocol-with-spring-ai-building-mcp-clients-and-servers-in-java-2146</guid>
      <description>&lt;h1&gt;
  
  
  Model Context Protocol with Spring AI: Building MCP Clients and Servers in Java
&lt;/h1&gt;

&lt;p&gt;In the previous article, we explored how to build AI agents with &lt;strong&gt;Spring AI&lt;/strong&gt; using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LLMs
 ↓
RAG
 ↓
Tool Calling
 ↓
Memory
 ↓
Agent Workflows
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tool calling gives an AI application the ability to interact with external capabilities.&lt;/p&gt;

&lt;p&gt;But another problem appears as AI systems become larger.&lt;/p&gt;

&lt;p&gt;Imagine you have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Customer Service Agent
        ↓
Order APIs
Payment APIs
CRM APIs
Knowledge Base
Email Service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And another application has:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sales Agent
        ↓
CRM
Calendar
Email
Customer Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And another has:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Developer Agent
        ↓
Git Repository
Issue Tracker
CI/CD
Documentation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If every AI application implements every integration differently, the architecture quickly becomes difficult to maintain.&lt;/p&gt;

&lt;p&gt;This is where &lt;strong&gt;Model Context Protocol (MCP)&lt;/strong&gt; becomes interesting.&lt;/p&gt;

&lt;p&gt;MCP provides a standardized way for AI applications to interact with external tools and resources. Spring AI provides support for both building MCP servers and consuming MCP servers from Spring Boot applications.&lt;/p&gt;

&lt;p&gt;In this article, we'll build a mental model for MCP and explore how Java developers can use it with Spring AI.&lt;/p&gt;




&lt;h1&gt;
  
  
  What Is MCP?
&lt;/h1&gt;

&lt;p&gt;MCP stands for:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Model Context Protocol&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;At a high level, MCP standardizes how an AI application communicates with external capabilities such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tools
Resources
Prompts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of every AI application inventing its own integration mechanism:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI Application
 ↓
Custom Tool Integration
 ↓
CRM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;we can have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI Application
 ↓
MCP Client
 ↓
MCP Protocol
 ↓
MCP Server
 ↓
CRM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The MCP server exposes capabilities through a standardized interface.&lt;/p&gt;

&lt;p&gt;The AI application doesn't need to understand every internal implementation detail of the external system.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why MCP Exists
&lt;/h1&gt;

&lt;p&gt;Suppose you build an AI assistant that needs access to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GitHub
Slack
PostgreSQL
Google Calendar
Internal APIs
File Systems
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without a standard protocol, your application might contain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GitHub Integration
Slack Integration
PostgreSQL Integration
Calendar Integration
Internal API Integration
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each integration may have its own:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Authentication
Tool Schema
Request Format
Response Format
Connection Management
Error Handling
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now imagine another AI application needs the same capabilities.&lt;/p&gt;

&lt;p&gt;You may end up rebuilding many of the same integrations.&lt;/p&gt;

&lt;p&gt;MCP addresses this by creating a common protocol for AI applications and external servers.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    AI Application
                         │
                    MCP Client
                         │
                MCP Protocol
                         │
       ┌─────────────────┼─────────────────┐
       ↓                 ↓                 ↓
  MCP Server         MCP Server         MCP Server
       ↓                 ↓                 ↓
     CRM              GitHub            Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is one of the main ideas behind MCP.&lt;/p&gt;




&lt;h1&gt;
  
  
  MCP Is Not an LLM
&lt;/h1&gt;

&lt;p&gt;This distinction is important.&lt;/p&gt;

&lt;p&gt;MCP is not:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;An AI model
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is a protocol for connecting AI applications with capabilities.&lt;/p&gt;

&lt;p&gt;Think of the stack like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LLM
 ↓
AI Application
 ↓
MCP Client
 ↓
MCP Protocol
 ↓
MCP Server
 ↓
Tools / Resources
 ↓
External System
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The LLM performs reasoning.&lt;/p&gt;

&lt;p&gt;The MCP layer provides standardized communication.&lt;/p&gt;

&lt;p&gt;The external system performs the actual operation.&lt;/p&gt;




&lt;h1&gt;
  
  
  MCP Client vs MCP Server
&lt;/h1&gt;

&lt;p&gt;MCP introduces two important roles.&lt;/p&gt;

&lt;h2&gt;
  
  
  MCP Client
&lt;/h2&gt;

&lt;p&gt;The MCP client lives inside the AI application.&lt;/p&gt;

&lt;p&gt;Its responsibility is to connect to MCP servers and interact with the capabilities they expose.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spring Boot AI Application
        ↓
MCP Client
        ↓
Weather MCP Server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The client can discover and use the server's available capabilities.&lt;/p&gt;

&lt;h2&gt;
  
  
  MCP Server
&lt;/h2&gt;

&lt;p&gt;The MCP server exposes capabilities.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Weather MCP Server

Tools:
getWeather()
getForecast()

Resources:
weather://cities

Prompts:
weather-analysis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server is responsible for implementing those capabilities.&lt;/p&gt;

&lt;p&gt;Spring AI provides Boot starters and APIs for both sides of this architecture.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Basic MCP Architecture
&lt;/h1&gt;

&lt;p&gt;A simplified architecture looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    User
                     ↓
                 Spring Boot
                     ↓
                  ChatClient
                     ↓
                  MCP Client
                     ↓
                MCP Protocol
                     ↓
                MCP Server
                     ↓
                   Tool
                     ↓
                External API
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User:
What's the weather in Paris?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AI application can discover a weather tool exposed by an MCP server.&lt;/p&gt;

&lt;p&gt;The flow becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User
 ↓
LLM
 ↓
MCP Tool
 ↓
Weather MCP Server
 ↓
Weather API
 ↓
Tool Result
 ↓
LLM
 ↓
Final Answer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  MCP and Traditional Tool Calling
&lt;/h1&gt;

&lt;p&gt;At this point, you might ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Isn't this just tool calling?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There is an important distinction.&lt;/p&gt;

&lt;p&gt;Traditional Spring AI tool calling can expose application methods directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Tool&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;getWeather&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;weatherService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getWeather&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your application owns the tool.&lt;/p&gt;

&lt;p&gt;With MCP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI Application
      ↓
MCP Client
      ↓
Remote MCP Server
      ↓
Tool
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tool can live outside the application.&lt;/p&gt;

&lt;p&gt;This creates a cleaner separation between:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI Application
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Capability Provider
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Spring AI integrates MCP tools into its tool-calling architecture, allowing applications to consume tools exposed by MCP servers.&lt;/p&gt;




&lt;h1&gt;
  
  
  MCP Tools
&lt;/h1&gt;

&lt;p&gt;One of the most important MCP capabilities is the &lt;strong&gt;tool&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A tool represents an action that an AI application can invoke.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;getWeather()
createTicket()
searchCustomers()
getOrder()
sendEmail()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A weather server might expose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;getTemperature(city)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A CRM server might expose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;findCustomer(email)
createLead(customer)
updateLead(leadId)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A developer server might expose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;searchRepository(query)
getBuildStatus()
createIssue(title)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The MCP client can discover these tools and make them available to the AI application.&lt;/p&gt;




&lt;h1&gt;
  
  
  MCP Resources
&lt;/h1&gt;

&lt;p&gt;MCP is not limited to actions.&lt;/p&gt;

&lt;p&gt;It can also expose &lt;strong&gt;resources&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A resource represents information that an MCP client can access.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;customer://123
order://ORD-10291
file://README.md
database://schema
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Think of the distinction as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tool
=
Do something

Resource
=
Access something
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tool:
createTicket()

Resource:
customer://123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A server can expose both.&lt;/p&gt;




&lt;h1&gt;
  
  
  MCP Prompts
&lt;/h1&gt;

&lt;p&gt;MCP also supports prompts.&lt;/p&gt;

&lt;p&gt;A server can provide reusable prompt templates for specific tasks.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Prompt:
analyze-customer

Input:
customerId
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Prompt:
summarize-order

Input:
orderId
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows prompt templates to become part of the server-provided capabilities rather than being hardcoded independently in every client.&lt;/p&gt;

&lt;p&gt;Spring AI's MCP support includes annotations for tools, resources, and prompts.&lt;/p&gt;




&lt;h1&gt;
  
  
  Building an MCP Server with Spring AI
&lt;/h1&gt;

&lt;p&gt;Let's build a simple MCP server.&lt;/p&gt;

&lt;p&gt;Imagine a weather service.&lt;/p&gt;

&lt;p&gt;Our application already has:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Service&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WeatherService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;getTemperature&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"22°C"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can expose this capability through an MCP tool.&lt;/p&gt;

&lt;p&gt;With Spring AI's annotation-based MCP support:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Service&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WeatherTools&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@McpTool&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Get the current temperature for a city"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;getTemperature&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="nd"&gt;@McpToolParam&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"City name"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
            &lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;weatherService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getTemperature&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The MCP annotation model allows Spring services to expose capabilities as MCP operations.&lt;/p&gt;




&lt;h1&gt;
  
  
  Creating the MCP Server
&lt;/h1&gt;

&lt;p&gt;For a Spring Boot application, Spring AI provides MCP server starters.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;You can configure the server to use Streamable HTTP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;spring.ai.mcp.server.protocol&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;STREAMABLE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Spring AI 2.x supports MCP server transports including Streamable HTTP, stateless Streamable HTTP, SSE, and STDIO. Streamable HTTP is the current recommended HTTP transport in Spring AI 2.x, while SSE is deprecated for this use case.&lt;/p&gt;




&lt;h1&gt;
  
  
  What Happens Inside the MCP Server?
&lt;/h1&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spring Boot
     ↓
MCP Server
     ↓
Tool Registry
     ↓
@McpTool
     ↓
WeatherService
     ↓
Weather API
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server exposes the tool through the MCP protocol.&lt;/p&gt;

&lt;p&gt;The client doesn't need to know how the weather service works internally.&lt;/p&gt;

&lt;p&gt;It only needs to understand:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tool Name
Description
Input Schema
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Building an MCP Client
&lt;/h1&gt;

&lt;p&gt;Now let's create the other side.&lt;/p&gt;

&lt;p&gt;Suppose our AI application needs to consume the weather MCP server.&lt;/p&gt;

&lt;p&gt;Add the MCP client starter:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Then configure the MCP server connection.&lt;/p&gt;

&lt;p&gt;For example, using Streamable HTTP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;spring&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ai&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;mcp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;client&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;streamable-http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;connections&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;weather-server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://localhost:8080&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Spring AI can connect to the configured MCP server and discover its tools.&lt;/p&gt;




&lt;h1&gt;
  
  
  Connecting MCP Tools to ChatClient
&lt;/h1&gt;

&lt;p&gt;Once the MCP client discovers the server's tools, those tools can be integrated into Spring AI's tool-calling architecture.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Bean&lt;/span&gt;
&lt;span class="nc"&gt;CommandLineRunner&lt;/span&gt; &lt;span class="nf"&gt;demo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;ChatClient&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;ToolCallbackProvider&lt;/span&gt; &lt;span class="n"&gt;mcpTools&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"What's the weather in Paris?"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mcpTools&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;};&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a powerful abstraction.&lt;/p&gt;

&lt;p&gt;The application doesn't need to manually implement every weather function.&lt;/p&gt;

&lt;p&gt;The MCP server provides the capability.&lt;/p&gt;

&lt;p&gt;The MCP client discovers it.&lt;/p&gt;

&lt;p&gt;Spring AI makes the discovered tools available to the model.&lt;/p&gt;

&lt;p&gt;The flow becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User
 ↓
ChatClient
 ↓
LLM
 ↓
MCP Tool
 ↓
MCP Client
 ↓
MCP Server
 ↓
Weather API
 ↓
Tool Result
 ↓
LLM
 ↓
Answer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Spring AI's current MCP documentation demonstrates this client pattern using &lt;code&gt;ToolCallbackProvider&lt;/code&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  MCP Tool Discovery
&lt;/h1&gt;

&lt;p&gt;One of the interesting capabilities of MCP is tool discovery.&lt;/p&gt;

&lt;p&gt;Instead of hardcoding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tool A
Tool B
Tool C
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the client can connect to an MCP server and discover what capabilities it provides.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MCP Server
 ↓
tools/list
 ↓
getWeather()
getForecast()
searchAlerts()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AI application can then make these tools available to the model.&lt;/p&gt;

&lt;p&gt;This creates a more modular architecture.&lt;/p&gt;




&lt;h1&gt;
  
  
  Multiple MCP Servers
&lt;/h1&gt;

&lt;p&gt;Now imagine our AI assistant needs multiple capabilities.&lt;/p&gt;

&lt;p&gt;We could have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI Application
      │
      ├── MCP Client
      │
      ├── Weather Server
      │
      ├── CRM Server
      │
      ├── GitHub Server
      │
      └── Internal API Server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The architecture becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                         AI Agent
                            │
                        MCP Client
                            │
             ┌──────────────┼──────────────┐
             ↓              ↓              ↓
        Weather MCP      CRM MCP       GitHub MCP
             ↓              ↓              ↓
        Weather API       CRM API      GitHub API
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AI application can consume tools from multiple MCP servers.&lt;/p&gt;

&lt;p&gt;This is one reason MCP becomes useful as an AI system grows.&lt;/p&gt;




&lt;h1&gt;
  
  
  MCP + Spring AI Agents
&lt;/h1&gt;

&lt;p&gt;Now connect this to the previous article.&lt;/p&gt;

&lt;p&gt;We previously had:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Agent
 ↓
Tools
 ↓
APIs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With MCP, we can move the tools outside the application boundary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Agent
 ↓
MCP Client
 ↓
MCP Servers
 ├── CRM
 ├── Payments
 ├── Search
 └── Internal APIs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The resulting architecture becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                         User
                           ↓
                       Spring Boot
                           ↓
                        ChatClient
                           ↓
                         Agent
                           ↓
                       MCP Client
                           ↓
             ┌─────────────┼─────────────┐
             ↓             ↓             ↓
          CRM MCP      Payment MCP    Search MCP
             ↓             ↓             ↓
           CRM API     Payment API   Search API
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a modular tool ecosystem.&lt;/p&gt;




&lt;h1&gt;
  
  
  MCP vs Direct Tool Calling
&lt;/h1&gt;

&lt;p&gt;Let's compare the two approaches.&lt;/p&gt;

&lt;h2&gt;
  
  
  Direct Spring AI Tool
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ChatClient
    ↓
@Tool
    ↓
Service
    ↓
Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything lives inside the application.&lt;/p&gt;

&lt;h2&gt;
  
  
  MCP Tool
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ChatClient
    ↓
MCP Client
    ↓
MCP Server
    ↓
Service
    ↓
Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The capability provider can be separated from the AI application.&lt;/p&gt;

&lt;p&gt;This can be useful when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multiple AI applications need the same capability&lt;/li&gt;
&lt;li&gt;Tools need independent deployment&lt;/li&gt;
&lt;li&gt;Teams own different integrations&lt;/li&gt;
&lt;li&gt;External systems need standardized AI access&lt;/li&gt;
&lt;li&gt;You want a reusable tool ecosystem&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  MCP Server as a Capability Layer
&lt;/h1&gt;

&lt;p&gt;One useful architectural pattern is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Business System
       ↓
MCP Server
       ↓
AI Applications
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CRM
 ↓
CRM MCP Server
 ↓
 ├── Sales Agent
 ├── Support Agent
 └── Internal Assistant
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of implementing CRM integration separately in every AI application, the MCP server becomes the standardized capability layer.&lt;/p&gt;




&lt;h1&gt;
  
  
  MCP + RAG
&lt;/h1&gt;

&lt;p&gt;MCP doesn't replace RAG.&lt;/p&gt;

&lt;p&gt;They solve different problems.&lt;/p&gt;

&lt;p&gt;RAG:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Retrieve relevant knowledge
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MCP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Connect AI applications to external capabilities
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can combine them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                       Agent
                         ↓
             ┌───────────┼───────────┐
             ↓           ↓           ↓
            RAG        MCP Tools    Memory
             ↓           ↓           ↓
        Vector DB    External APIs  Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User:
Can I refund order ORD-10291?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent could:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. MCP → Get order information
2. RAG → Retrieve refund policy
3. Agent → Compare the two
4. Return answer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives the model both:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Live Data
+
Business Knowledge
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  MCP + Memory
&lt;/h1&gt;

&lt;p&gt;Memory can also coexist with MCP.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User:
Use my preferred delivery address.

Agent:
Which address?

User:
The one I used last time.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application may use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Memory
 ↓
Previous Address
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;while MCP provides:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Order Service
 ↓
Update Delivery Address
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The complete flow becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Agent
 ├── Memory
 ├── RAG
 └── MCP
       ├── Orders
       ├── Payments
       └── CRM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is becoming a much more complete agent architecture.&lt;/p&gt;




&lt;h1&gt;
  
  
  MCP Transports
&lt;/h1&gt;

&lt;p&gt;MCP supports multiple ways for clients and servers to communicate.&lt;/p&gt;

&lt;p&gt;Common options include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;STDIO
SSE
Streamable HTTP
Stateless Streamable HTTP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For local process-based integrations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI Application
 ↓
STDIO
 ↓
MCP Server Process
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For network-based applications:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI Application
 ↓
HTTP
 ↓
MCP Server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Spring AI 2.x, Streamable HTTP is the current HTTP-oriented approach, while SSE has been deprecated in favor of Streamable HTTP.&lt;/p&gt;




&lt;h1&gt;
  
  
  STDIO vs HTTP
&lt;/h1&gt;

&lt;p&gt;A simple way to think about it:&lt;/p&gt;

&lt;h3&gt;
  
  
  STDIO
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application
 ↓
Local MCP Process
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Useful for local integrations and process-based communication.&lt;/p&gt;

&lt;h3&gt;
  
  
  Streamable HTTP
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application
 ↓
Network
 ↓
MCP Server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Useful when the MCP server runs as an independent service.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stateless Streamable HTTP
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client
 ↓
Request
 ↓
Server
 ↓
Response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This can be useful for stateless, cloud-native service architectures.&lt;/p&gt;

&lt;p&gt;The right transport depends on deployment and communication requirements.&lt;/p&gt;




&lt;h1&gt;
  
  
  MCP Security
&lt;/h1&gt;

&lt;p&gt;This is extremely important.&lt;/p&gt;

&lt;p&gt;An MCP server may expose powerful capabilities:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;readCustomer()
createInvoice()
refundPayment()
deleteUser()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simply exposing those tools does not make them safe.&lt;/p&gt;

&lt;p&gt;Spring AI's MCP server starters do not automatically provide authentication or authorization for network-accessible MCP endpoints. The documentation specifically warns that HTTP-based MCP endpoints need a security boundary before being exposed beyond localhost.&lt;/p&gt;

&lt;p&gt;A production architecture should look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client
 ↓
Authentication
 ↓
Authorization
 ↓
MCP Server
 ↓
Tool
 ↓
Business Logic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Internet
 ↓
MCP Server
 ↓
Dangerous Tool
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  MCP Tool Authorization
&lt;/h1&gt;

&lt;p&gt;Imagine an MCP server exposes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;getCustomer()
updateCustomer()
deleteCustomer()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Different users should have different capabilities.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;READ
 ↓
getCustomer()

WRITE
 ↓
updateCustomer()

DESTRUCTIVE
 ↓
deleteCustomer()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your security layer should determine whether the caller is allowed to invoke each capability.&lt;/p&gt;

&lt;p&gt;The model should never be considered the authorization layer.&lt;/p&gt;

&lt;p&gt;The application must enforce it.&lt;/p&gt;




&lt;h1&gt;
  
  
  MCP and Multi-Tenant Systems
&lt;/h1&gt;

&lt;p&gt;MCP becomes particularly interesting in SaaS environments.&lt;/p&gt;

&lt;p&gt;Suppose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tenant A
 ↓
CRM MCP Server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tenant B
 ↓
CRM MCP Server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The MCP layer must preserve tenant context.&lt;/p&gt;

&lt;p&gt;A request might carry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tenantId
userId
roles
permissions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server can then enforce:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Authentication
 ↓
Tenant Resolution
 ↓
Authorization
 ↓
Tool Execution
 ↓
Tenant-Scoped Data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is especially important for tools such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;searchCustomers()
getInvoices()
searchDocuments()
createTicket()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A model must never be able to use a tool to cross tenant boundaries.&lt;/p&gt;




&lt;h1&gt;
  
  
  MCP Error Handling
&lt;/h1&gt;

&lt;p&gt;External tools can fail.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Agent
 ↓
MCP Tool
 ↓
CRM API
 ↓
Timeout
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your application needs controlled failure behavior.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tool Failure
 ↓
Capture Error
 ↓
Return Structured Result
 ↓
Agent
 ↓
Retry / Alternative Tool / Final Response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent might decide:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CRM unavailable.

Try cached customer information.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Unable to retrieve the customer's order.
Please try again later.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part is that failures should be observable and controlled.&lt;/p&gt;




&lt;h1&gt;
  
  
  MCP Observability
&lt;/h1&gt;

&lt;p&gt;When MCP is added to an agent architecture, your observability requirements increase.&lt;/p&gt;

&lt;p&gt;You may need to track:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MCP Server
MCP Client
Tool Name
Tool Arguments
Request ID
Latency
Status
Errors
Retries
Model Calls
Token Usage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A useful trace could look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User Request
    ↓
LLM Call
    ↓
MCP Tool Discovery
    ↓
Tool Call
    ↓
CRM API
    ↓
Tool Result
    ↓
LLM Call
    ↓
Final Response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without tracing, debugging multi-server agent systems can become difficult.&lt;/p&gt;




&lt;h1&gt;
  
  
  MCP Doesn't Replace Your Business Logic
&lt;/h1&gt;

&lt;p&gt;This is another important principle.&lt;/p&gt;

&lt;p&gt;Suppose you have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;RefundResult&lt;/span&gt; &lt;span class="nf"&gt;refundPayment&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;BigDecimal&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You shouldn't move all business logic into an MCP handler.&lt;/p&gt;

&lt;p&gt;Instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MCP Tool
 ↓
Application Service
 ↓
Business Rules
 ↓
Repository
 ↓
Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@McpTool&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Refund an eligible order"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;RefundResult&lt;/span&gt; &lt;span class="nf"&gt;refundOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;refundService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;refund&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The MCP layer becomes an interface.&lt;/p&gt;

&lt;p&gt;Your existing business service remains responsible for the actual business rules.&lt;/p&gt;

&lt;p&gt;This keeps the architecture clean.&lt;/p&gt;




&lt;h1&gt;
  
  
  MCP as an Integration Boundary
&lt;/h1&gt;

&lt;p&gt;One of the strongest ways to think about MCP is as an integration boundary.&lt;/p&gt;

&lt;p&gt;Instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI
 ↓
Everything
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI
 ↓
MCP
 ↓
Controlled Capabilities
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The MCP layer becomes a contract between AI applications and external systems.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI Application
      ↓
MCP
      ↓
CRM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI Application
      ↓
MCP
      ↓
Payment System
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI Application
      ↓
MCP
      ↓
Internal Developer Platform
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  A Complete Spring AI + MCP Architecture
&lt;/h1&gt;

&lt;p&gt;Now combine everything from this series:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                              User
                                ↓
                         Spring Boot API
                                ↓
                           ChatClient
                                ↓
                              Agent
                                ↓
             ┌──────────────────┼──────────────────┐
             ↓                  ↓                  ↓
           Memory              RAG              MCP Client
             ↓                  ↓                  ↓
         PostgreSQL          pgvector        ┌─────┼─────┐
                                             ↓     ↓     ↓
                                           CRM  GitHub  Search
                                           MCP    MCP     MCP
                                             ↓     ↓     ↓
                                           APIs  APIs   APIs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Around the system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Authentication
Authorization
Tenant Isolation
Observability
Audit Logging
Rate Limiting
Guardrails
Human Approval
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a strong foundation for production-oriented AI applications.&lt;/p&gt;




&lt;h1&gt;
  
  
  When Should You Use MCP?
&lt;/h1&gt;

&lt;p&gt;MCP becomes particularly useful when you have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Multiple AI applications
        ↓
Shared tools
        ↓
Shared integrations
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sales Agent
Support Agent
Developer Agent
Internal Assistant
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;all need access to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CRM
GitHub
Internal APIs
Documentation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of implementing each integration separately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Agent A → CRM Integration
Agent B → CRM Integration
Agent C → CRM Integration
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you can create:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CRM MCP Server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and allow multiple AI applications to consume it.&lt;/p&gt;




&lt;h1&gt;
  
  
  When You Don't Need MCP
&lt;/h1&gt;

&lt;p&gt;MCP isn't automatically required for every AI application.&lt;/p&gt;

&lt;p&gt;If your application has:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;One Agent
 ↓
One Tool
 ↓
One Internal Service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;direct Spring AI tool calling may be simpler.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ChatClient
 ↓
@Tool
 ↓
OrderService
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Introducing an MCP server could add unnecessary infrastructure.&lt;/p&gt;

&lt;p&gt;A useful rule is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Use MCP when standardization, reuse, separation, or interoperability provides real value.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Don't introduce another protocol simply because it is popular.&lt;/p&gt;




&lt;h1&gt;
  
  
  Direct Tools vs MCP
&lt;/h1&gt;

&lt;p&gt;A simple comparison:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Best suited for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Spring AI &lt;code&gt;@Tool&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Local application capabilities&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MCP&lt;/td&gt;
&lt;td&gt;Shared/external capabilities&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RAG&lt;/td&gt;
&lt;td&gt;Knowledge retrieval&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory&lt;/td&gt;
&lt;td&gt;Conversation context&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent&lt;/td&gt;
&lt;td&gt;Dynamic decision-making&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;They are not mutually exclusive.&lt;/p&gt;

&lt;p&gt;A production system may use all of them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Agent
 ├── Local Spring AI Tools
 ├── MCP Tools
 ├── RAG
 └── Memory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  The Bigger Picture
&lt;/h1&gt;

&lt;p&gt;Our AI architecture has evolved throughout this series.&lt;/p&gt;

&lt;p&gt;We started with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LLM
 ↓
Response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LLM
 ↓
RAG
 ↓
Knowledge
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LLM
 ↓
Tools
 ↓
Actions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LLM
 ↓
Tools
 ↓
Memory
 ↓
Agent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Agent
 ↓
MCP
 ↓
External Capabilities
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The architecture is becoming increasingly modular.&lt;/p&gt;




&lt;h1&gt;
  
  
  A Mental Model for MCP
&lt;/h1&gt;

&lt;p&gt;Remember it this way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LLM
=
Reason

RAG
=
Retrieve Knowledge

Memory
=
Remember Context

Tool Calling
=
Invoke Capabilities

MCP
=
Standardize Capability Access

Spring Boot
=
Business Application

Agent
=
Coordinate Decisions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LLM
+
RAG
+
Memory
+
Tools
+
MCP
+
Business Logic
=
Production AI Application
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Final Takeaways
&lt;/h1&gt;

&lt;p&gt;MCP gives AI applications a standardized way to interact with external tools and resources.&lt;/p&gt;

&lt;p&gt;The key ideas are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MCP Client&lt;/strong&gt; connects an AI application to MCP servers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP Server&lt;/strong&gt; exposes tools, resources, and prompts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tools&lt;/strong&gt; allow actions to be performed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resources&lt;/strong&gt; provide access to information.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompts&lt;/strong&gt; can provide reusable prompt templates.&lt;/li&gt;
&lt;li&gt;Spring AI supports MCP clients and servers through Boot starters and annotations.&lt;/li&gt;
&lt;li&gt;MCP tools integrate with Spring AI's existing tool-calling architecture.&lt;/li&gt;
&lt;li&gt;Streamable HTTP is the current HTTP-oriented transport in Spring AI 2.x.&lt;/li&gt;
&lt;li&gt;MCP does not replace your business logic.&lt;/li&gt;
&lt;li&gt;Authentication and authorization must be enforced before exposing network-accessible MCP servers.&lt;/li&gt;
&lt;li&gt;Multi-tenant applications must preserve tenant isolation across MCP calls.&lt;/li&gt;
&lt;li&gt;MCP is particularly useful when capabilities need to be shared across multiple AI applications.&lt;/li&gt;
&lt;li&gt;For simple local integrations, direct Spring AI tools may be sufficient.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The architecture can now look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                         User
                           ↓
                        Agent
                           ↓
        ┌──────────────────┼──────────────────┐
        ↓                  ↓                  ↓
      Memory              RAG             MCP Client
        ↓                  ↓                  ↓
    Conversation       Vector DB       MCP Servers
                                             ↓
                              ┌──────────────┼──────────────┐
                              ↓              ↓              ↓
                             CRM          GitHub         Internal APIs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important shift is this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Before:

AI Application
 ↓
Custom Integrations
 ↓
External Systems
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With MCP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI Application
 ↓
MCP Client
 ↓
Standardized Protocol
 ↓
MCP Servers
 ↓
External Capabilities
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MCP doesn't make your AI application automatically intelligent.&lt;/p&gt;

&lt;p&gt;It gives your AI application a &lt;strong&gt;standardized way to connect to capabilities&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And when you combine MCP with Spring AI's:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ChatClient
+
Tool Calling
+
RAG
+
Memory
+
Agents
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you get a powerful foundation for building modular AI applications in Java.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;We've now connected our AI agent to external capabilities.&lt;/p&gt;

&lt;p&gt;But another challenge appears:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;One Agent
      ↓
Multiple MCP Servers
      ↓
Multiple Tools
      ↓
Multiple Decisions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How do we control which tools an agent can access?&lt;/p&gt;

&lt;p&gt;How do we handle permissions?&lt;/p&gt;

&lt;p&gt;How do we observe agent behavior?&lt;/p&gt;

&lt;p&gt;How do we evaluate whether an agent is making the right decisions?&lt;/p&gt;

&lt;p&gt;And how do we build reliable AI workflows instead of simply hoping the model does the right thing?&lt;/p&gt;

&lt;p&gt;That takes us into the next stage of AI engineering:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Building Production-Ready AI Agents with Spring AI — Guardrails, Evaluation, Observability, and Human-in-the-Loop Workflows.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>java</category>
      <category>springboot</category>
    </item>
    <item>
      <title>Building AI Agents with Spring AI — Tool Calling, Memory, and Autonomous Workflows</title>
      <dc:creator>Ayush Shrivastava</dc:creator>
      <pubDate>Mon, 07 Sep 2026 08:41:41 +0000</pubDate>
      <link>https://dev.to/ayshriv/building-ai-agents-with-spring-ai-tool-calling-memory-and-autonomous-workflows-2kgj</link>
      <guid>https://dev.to/ayshriv/building-ai-agents-with-spring-ai-tool-calling-memory-and-autonomous-workflows-2kgj</guid>
      <description>&lt;h1&gt;
  
  
  Building AI Agents with Spring AI — Tool Calling, Memory, and Autonomous Workflows
&lt;/h1&gt;

&lt;p&gt;Large Language Models are excellent at generating text.&lt;/p&gt;

&lt;p&gt;But generation alone isn't enough to build truly useful AI applications.&lt;/p&gt;

&lt;p&gt;Imagine asking an AI assistant:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;What's the status of my order?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A normal LLM can explain how order tracking works.&lt;/p&gt;

&lt;p&gt;But it cannot magically access your order database.&lt;/p&gt;

&lt;p&gt;Or suppose you ask:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cancel my order #ORD-10291.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model can tell you how to cancel an order.&lt;/p&gt;

&lt;p&gt;But it cannot actually cancel anything unless your application gives it the ability to perform that action.&lt;/p&gt;

&lt;p&gt;This is where &lt;strong&gt;tool calling&lt;/strong&gt; and &lt;strong&gt;AI agents&lt;/strong&gt; come in.&lt;/p&gt;

&lt;p&gt;Instead of simply generating an answer, an AI application can:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Understand the request
        ↓
Decide what action is required
        ↓
Select a tool
        ↓
Execute the tool
        ↓
Observe the result
        ↓
Continue reasoning
        ↓
Generate the final response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this article, we'll explore how to build this architecture using &lt;strong&gt;Spring AI&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  What Is an AI Agent?
&lt;/h1&gt;

&lt;p&gt;An AI agent is an application where an LLM can decide what actions need to be performed and use available tools to accomplish a goal.&lt;/p&gt;

&lt;p&gt;A traditional LLM application looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User
 ↓
Prompt
 ↓
LLM
 ↓
Response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An agent-based application looks more like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User
 ↓
LLM
 ↓
Decision
 ↓
Tool
 ↓
Result
 ↓
LLM
 ↓
Decision
 ↓
Another Tool
 ↓
Result
 ↓
Final Answer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important difference is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The LLM is no longer limited to generating text.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It can interact with the application through controlled capabilities.&lt;/p&gt;




&lt;h1&gt;
  
  
  Tool Calling
&lt;/h1&gt;

&lt;p&gt;Tool calling allows an LLM to request the execution of a function exposed by your application.&lt;/p&gt;

&lt;p&gt;For example, imagine our application provides:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;getOrderStatus()
cancelOrder()
getCustomer()
createSupportTicket()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The user asks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Where is my order?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model might determine that it needs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;getOrderStatus()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application executes the function and returns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Order #10291
Status: Shipped
Expected delivery: September 10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The LLM can then generate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your order has been shipped and is expected to arrive on September 10.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The LLM didn't directly access the database.&lt;/p&gt;

&lt;p&gt;Instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LLM
 ↓
Tool Request
 ↓
Application
 ↓
Database
 ↓
Tool Result
 ↓
LLM
 ↓
Answer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This distinction is extremely important for enterprise applications.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Tool Calling Matters
&lt;/h1&gt;

&lt;p&gt;Without tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LLM
 ↓
Text
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LLM
 ↓
Tools
 ├── Database
 ├── REST APIs
 ├── Search
 ├── Payment systems
 ├── CRM
 ├── Internal services
 └── Business workflows
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This turns the LLM from a text-generation component into an interface for interacting with your application.&lt;/p&gt;

&lt;p&gt;For example, an AI sales assistant could have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;getCustomer()
getCustomerOrders()
createLead()
updateLead()
sendEmail()
scheduleMeeting()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A support agent could have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;searchKnowledgeBase()
getCustomerAccount()
getOrder()
createTicket()
updateTicket()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An internal developer assistant could have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;searchDocumentation()
searchGitRepository()
getBuildStatus()
createIssue()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The possibilities are much broader than simple question answering.&lt;/p&gt;




&lt;h1&gt;
  
  
  RAG vs Tool Calling
&lt;/h1&gt;

&lt;p&gt;At this point, it is useful to distinguish RAG from tool calling.&lt;/p&gt;

&lt;p&gt;RAG is primarily about &lt;strong&gt;retrieving information&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Tool calling is about &lt;strong&gt;performing actions or retrieving live data through application capabilities&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RAG
 ↓
Retrieve company documentation
 ↓
Answer question
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tool calling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LLM
 ↓
Call order API
 ↓
Get live order status
 ↓
Answer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They can also be combined.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User
 ↓
AI Agent
 ├── RAG → Search company policies
 │
 ├── Tool → Get customer account
 │
 └── Tool → Check order status
          ↓
       LLM
          ↓
       Answer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This combination is extremely powerful.&lt;/p&gt;




&lt;h1&gt;
  
  
  Spring AI and Tool Calling
&lt;/h1&gt;

&lt;p&gt;Spring AI provides abstractions that make it easier to expose application capabilities to language models.&lt;/p&gt;

&lt;p&gt;A simplified architecture looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spring Boot
     │
     ├── ChatModel
     │
     ├── Tools
     │
     ├── Advisors
     │
     ├── Chat Memory
     │
     └── Vector Store
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application controls which tools are available.&lt;/p&gt;

&lt;p&gt;The model decides whether a tool is needed.&lt;/p&gt;

&lt;p&gt;This separation is important.&lt;/p&gt;

&lt;p&gt;The model should not have unrestricted access to your application.&lt;/p&gt;

&lt;p&gt;Instead, the application exposes specific capabilities.&lt;/p&gt;




&lt;h1&gt;
  
  
  Creating a Tool
&lt;/h1&gt;

&lt;p&gt;Imagine we have an order service.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Service&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;OrderStatus&lt;/span&gt; &lt;span class="nf"&gt;getOrderStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Fetch order from database&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;orderRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can expose a controlled method as an AI tool.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Tool&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Get the current status of an order"&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;OrderStatus&lt;/span&gt; &lt;span class="nf"&gt;getOrderStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;orderService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getOrderStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The description is important.&lt;/p&gt;

&lt;p&gt;The model uses the tool description to understand:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;What does this tool do?
When should I use it?
What parameters does it require?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tool:

getOrderStatus

Description:
Returns the current shipping and delivery status
for a customer order.

Input:
orderId
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model can then determine whether this tool is appropriate.&lt;/p&gt;




&lt;h1&gt;
  
  
  Tool Schema
&lt;/h1&gt;

&lt;p&gt;A tool can be thought of as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tool Name
     +
Description
     +
Input Schema
     +
Execution Logic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;getOrderStatus(
    orderId: String
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model might produce a tool request conceptually like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"getOrderStatus"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"arguments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"orderId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ORD-10291"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application receives the request and executes the corresponding Java method.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Tool Calling Loop
&lt;/h1&gt;

&lt;p&gt;A typical interaction looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User
 │
 │ "What's the status of ORD-10291?"
 ↓
LLM
 │
 │ Tool Request
 ↓
getOrderStatus("ORD-10291")
 │
 ↓
Order Service
 │
 ↓
Database
 │
 ↓
Tool Result
 │
 ↓
LLM
 │
 ↓
Final Answer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice something important.&lt;/p&gt;

&lt;p&gt;The LLM doesn't execute Java code itself.&lt;/p&gt;

&lt;p&gt;The application remains responsible for execution.&lt;/p&gt;

&lt;p&gt;The model only requests the action.&lt;/p&gt;




&lt;h1&gt;
  
  
  Tool Calling with ChatClient
&lt;/h1&gt;

&lt;p&gt;Spring AI's &lt;code&gt;ChatClient&lt;/code&gt; provides a convenient API for interacting with chat models.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;ChatClient&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"What's the status of order ORD-10291?"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orderTools&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact APIs may vary depending on the Spring AI version you're using, but the architecture remains the same:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ChatClient
   ↓
ChatModel
   ↓
Tool Selection
   ↓
Tool Execution
   ↓
Tool Result
   ↓
Final Response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Multiple Tools
&lt;/h1&gt;

&lt;p&gt;A real agent usually has more than one tool.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CustomerAgentTools

├── getCustomer()
├── getCustomerOrders()
├── getOrderStatus()
├── createSupportTicket()
└── updateCustomer()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now consider this question:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;My order is late. Please check the status
and create a support ticket if necessary.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model might determine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. getOrderStatus()
2. Analyze result
3. createSupportTicket()
4. Return final response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application executes each requested operation.&lt;/p&gt;

&lt;p&gt;This is where the concept of an agent starts becoming much more interesting.&lt;/p&gt;




&lt;h1&gt;
  
  
  Agents and Decision Making
&lt;/h1&gt;

&lt;p&gt;A simple agent loop can be represented as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;              ┌───────────────┐
              │     User      │
              └───────┬───────┘
                      ↓
                ┌───────────┐
                │    LLM    │
                └─────┬─────┘
                      ↓
                Need a Tool?
                /          \
              No            Yes
              ↓              ↓
          Final Answer    Tool Call
                             ↓
                        Tool Execution
                             ↓
                         Tool Result
                             ↓
                            LLM
                             ↓
                      Need Another Tool?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model can repeatedly interact with tools until it has enough information to produce the final response.&lt;/p&gt;




&lt;h1&gt;
  
  
  Tool Calling Is Not Full Autonomy
&lt;/h1&gt;

&lt;p&gt;This distinction is important.&lt;/p&gt;

&lt;p&gt;People often hear:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;AI Agent&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;and immediately think:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Give AI access to everything
        ↓
Let AI do whatever it wants
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is not how production systems should be designed.&lt;/p&gt;

&lt;p&gt;A production agent should operate inside clear boundaries.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Allowed Tools
     ↓
Authorization
     ↓
Validation
     ↓
Execution
     ↓
Audit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application remains in control.&lt;/p&gt;

&lt;p&gt;The model should not be trusted with unrestricted capabilities.&lt;/p&gt;




&lt;h1&gt;
  
  
  Tool Security
&lt;/h1&gt;

&lt;p&gt;Imagine we expose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Tool&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;deleteCustomer&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;customerId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is potentially dangerous.&lt;/p&gt;

&lt;p&gt;An LLM should not automatically receive unrestricted permission to perform destructive operations.&lt;/p&gt;

&lt;p&gt;Instead, sensitive tools should have additional controls.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User
 ↓
Authentication
 ↓
Authorization
 ↓
Agent
 ↓
Tool Request
 ↓
Permission Check
 ↓
Confirmation
 ↓
Execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For destructive operations, you may require explicit user confirmation.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI:
I found customer account C-19291.

Deleting this account is irreversible.
Do you want me to continue?

User:
Yes.

AI:
Executing deletion...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AI should assist with the decision process, not bypass your security model.&lt;/p&gt;




&lt;h1&gt;
  
  
  Tool Permissions
&lt;/h1&gt;

&lt;p&gt;A useful production architecture is to classify tools.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;READ

getCustomer()
getOrder()
searchDocuments()
getInvoice()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WRITE

createTicket()
updateCustomer()
createLead()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DESTRUCTIVE

deleteCustomer()
cancelSubscription()
refundPayment()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Different permission levels can then be applied.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;READ
→ Automatically allowed

WRITE
→ Role-based authorization

DESTRUCTIVE
→ Authorization + confirmation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes agent behavior much safer.&lt;/p&gt;




&lt;h1&gt;
  
  
  AI Agent Memory
&lt;/h1&gt;

&lt;p&gt;Tool calling solves one problem.&lt;/p&gt;

&lt;p&gt;But another problem appears quickly:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What does the agent remember?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Consider this conversation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User:
My order is late.

AI:
What's your order number?

User:
ORD-10291.

AI:
Let me check it.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the next message is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Can you create a support ticket for it?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AI needs to understand that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"it"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;refers to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ORD-10291
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This requires conversational context.&lt;/p&gt;

&lt;p&gt;That's where &lt;strong&gt;chat memory&lt;/strong&gt; becomes important.&lt;/p&gt;




&lt;h1&gt;
  
  
  Chat Memory
&lt;/h1&gt;

&lt;p&gt;A simple conversation can be represented as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User:
My order is late.

Assistant:
What's your order number?

User:
ORD-10291.

Assistant:
Let me check that order.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application maintains the conversation history.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Conversation ID
       ↓
Chat Memory
       ↓
Previous Messages
       ↓
Current Prompt
       ↓
LLM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Spring AI provides abstractions for managing chat memory.&lt;/p&gt;




&lt;h1&gt;
  
  
  Short-Term vs Long-Term Memory
&lt;/h1&gt;

&lt;p&gt;It is useful to distinguish two concepts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Short-Term Memory
&lt;/h3&gt;

&lt;p&gt;Conversation context.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User:
My order is late.

User:
It's order 10291.

User:
Can you check it?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The system remembers the current conversation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Long-Term Memory
&lt;/h3&gt;

&lt;p&gt;Persistent information about the user.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Customer:
Ayush

Preferences:
Preferred language = English
Preferred notification = Email
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Long-term memory usually requires persistence in a database or another storage system.&lt;/p&gt;

&lt;p&gt;A production architecture might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Conversation
     ↓
Chat Memory Store
     ↓
PostgreSQL / Redis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact storage mechanism depends on the application.&lt;/p&gt;




&lt;h1&gt;
  
  
  Agents + Memory
&lt;/h1&gt;

&lt;p&gt;Now we can combine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User
 ↓
Agent
 ↓
Memory
 ↓
LLM
 ↓
Tools
 ↓
Tool Results
 ↓
Memory
 ↓
LLM
 ↓
Answer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This enables more natural multi-turn interactions.&lt;/p&gt;




&lt;h1&gt;
  
  
  Advisors
&lt;/h1&gt;

&lt;p&gt;Another important Spring AI concept is the &lt;strong&gt;Advisor&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Advisors can intercept and influence the interaction between the application and the model.&lt;/p&gt;

&lt;p&gt;They can be used for concerns such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Conversation memory
RAG
Logging
Security
Prompt modification
Context injection
Observability
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User
 ↓
ChatClient
 ↓
Advisor
 ↓
ChatModel
 ↓
Advisor
 ↓
Response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows cross-cutting AI behavior to be separated from business logic.&lt;/p&gt;




&lt;h1&gt;
  
  
  Combining RAG + Tools + Memory
&lt;/h1&gt;

&lt;p&gt;Now things become much more powerful.&lt;/p&gt;

&lt;p&gt;Imagine an enterprise support agent.&lt;/p&gt;

&lt;p&gt;It has:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RAG
 ↓
Company documentation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;getCustomer()
getOrder()
createTicket()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Memory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Conversation history
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The architecture becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    User
                     ↓
                 AI Agent
                     ↓
             ┌───────┼────────┐
             ↓       ↓        ↓
            RAG    Tools    Memory
             ↓       ↓        ↓
        Knowledge   APIs   Conversation
             │       │        │
             └───────┼────────┘
                     ↓
                    LLM
                     ↓
                  Response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is much closer to a production AI application.&lt;/p&gt;




&lt;h1&gt;
  
  
  Example: Customer Support Agent
&lt;/h1&gt;

&lt;p&gt;Consider the request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;My payment failed for order ORD-19291.
Can you check what happened and tell me
what I should do?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent could perform:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. getOrder("ORD-19291")
2. getPaymentStatus("ORD-19291")
3. searchKnowledgeBase("payment failure")
4. Generate explanation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final answer could be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your payment attempt failed because the transaction
was declined by the payment provider.

According to the payment policy, you can retry the
payment using another payment method.

Would you like me to create a support ticket?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model combined:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Live application data
+
Knowledge base
+
Conversation context
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is significantly more useful than a standalone chatbot.&lt;/p&gt;




&lt;h1&gt;
  
  
  Multi-Step Agent Workflow
&lt;/h1&gt;

&lt;p&gt;Agents can also perform multi-step workflows.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User:
Find my overdue invoices and send reminders.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent could reason through:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;getCustomer()
      ↓
getInvoices()
      ↓
Filter overdue invoices
      ↓
sendReminder()
      ↓
Return summary
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The workflow becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Goal
 ↓
Plan
 ↓
Tool
 ↓
Observe
 ↓
Next Decision
 ↓
Tool
 ↓
Observe
 ↓
Final Result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pattern is often called an &lt;strong&gt;agent loop&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  Agent Loop
&lt;/h1&gt;

&lt;p&gt;A simplified conceptual implementation looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;completed&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nc"&gt;AgentDecision&lt;/span&gt; &lt;span class="n"&gt;decision&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
            &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;decide&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;requiresTool&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="nc"&gt;ToolResult&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
                &lt;span class="n"&gt;toolExecutor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;execute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                        &lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toolCall&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;finalAnswer&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In real applications, frameworks handle much of this interaction.&lt;/p&gt;

&lt;p&gt;But understanding the underlying loop is important.&lt;/p&gt;




&lt;h1&gt;
  
  
  Don't Build Everything as an Agent
&lt;/h1&gt;

&lt;p&gt;An important engineering lesson:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Not every AI feature needs an agent.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If your workflow is deterministic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Validate request
 ↓
Call API
 ↓
Save result
 ↓
Return response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you probably don't need an autonomous agent.&lt;/p&gt;

&lt;p&gt;A normal service workflow may be better.&lt;/p&gt;

&lt;p&gt;Agents become more useful when:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The next step depends on the current result.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Check order
 ↓
If delayed
 ↓
Check refund policy
 ↓
If eligible
 ↓
Ask for confirmation
 ↓
Create refund request
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The dynamic decision-making is where agents become valuable.&lt;/p&gt;




&lt;h1&gt;
  
  
  Deterministic Workflow vs Agent
&lt;/h1&gt;

&lt;h3&gt;
  
  
  Traditional Workflow
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A → B → C → D
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything is predetermined.&lt;/p&gt;

&lt;h3&gt;
  
  
  Agent Workflow
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A
 ↓
LLM decides
 ├── B
 ├── C
 └── D
      ↓
   Observe result
      ↓
   Decide again
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agents provide flexibility.&lt;/p&gt;

&lt;p&gt;Traditional workflows provide predictability.&lt;/p&gt;

&lt;p&gt;Production systems often use both.&lt;/p&gt;




&lt;h1&gt;
  
  
  Agent Architecture for Enterprise Java
&lt;/h1&gt;

&lt;p&gt;A practical Spring Boot architecture might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    ┌───────────────┐
                    │   Frontend    │
                    └───────┬───────┘
                            ↓
                    ┌───────────────┐
                    │ Spring Boot   │
                    │     API       │
                    └───────┬───────┘
                            ↓
                     ┌────────────┐
                     │ ChatClient │
                     └─────┬──────┘
                           ↓
                    ┌──────────────┐
                    │    Agent     │
                    └──────┬───────┘
                           ↓
              ┌────────────┼────────────┐
              ↓            ↓            ↓
           Memory         RAG         Tools
              ↓            ↓            ↓
          PostgreSQL    pgvector      APIs
                                         ↓
                                    Microservices
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This architecture fits naturally into existing Spring Boot applications.&lt;/p&gt;




&lt;h1&gt;
  
  
  Observability
&lt;/h1&gt;

&lt;p&gt;Agent systems can become difficult to debug.&lt;/p&gt;

&lt;p&gt;Imagine an agent performs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tool 1
Tool 2
Tool 3
Tool 4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and the final response is incorrect.&lt;/p&gt;

&lt;p&gt;You need to know:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;What did the model decide?
Which tools were selected?
What arguments were sent?
How long did each tool take?
What did each tool return?
How many model calls happened?
How many tokens were consumed?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Therefore, observability is critical.&lt;/p&gt;

&lt;p&gt;Track:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LLM latency
Tool latency
Retrieval latency
Token usage
Tool calls
Tool failures
Model responses
Agent iterations
Errors
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Preventing Infinite Agent Loops
&lt;/h1&gt;

&lt;p&gt;An agent can potentially continue calling tools indefinitely.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LLM
 ↓
Tool
 ↓
LLM
 ↓
Tool
 ↓
LLM
 ↓
Tool
 ↓
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Production systems should enforce limits.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Maximum iterations = 10
Maximum tool calls = 20
Maximum execution time = 30 seconds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should also define clear failure behavior.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Agent limit reached
        ↓
Stop execution
        ↓
Return safe response
        ↓
Log failure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Tool Validation
&lt;/h1&gt;

&lt;p&gt;Never blindly trust model-generated tool arguments.&lt;/p&gt;

&lt;p&gt;Suppose the model requests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"orderId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ORD-999999999"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your application should still validate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Does the order exist?
Does the user own the order?
Is the user authorized?
Is the order accessible to this tenant?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The architecture should be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LLM
 ↓
Tool Request
 ↓
Schema Validation
 ↓
Authorization
 ↓
Business Validation
 ↓
Tool Execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The LLM is not your security boundary.&lt;/p&gt;

&lt;p&gt;Your application is.&lt;/p&gt;




&lt;h1&gt;
  
  
  Multi-Tenant AI Agents
&lt;/h1&gt;

&lt;p&gt;This becomes especially important in SaaS applications.&lt;/p&gt;

&lt;p&gt;Imagine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tenant A
 ├── Customers
 ├── Orders
 └── Documents

Tenant B
 ├── Customers
 ├── Orders
 └── Documents
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An AI agent must never retrieve Tenant B's information while processing a Tenant A request.&lt;/p&gt;

&lt;p&gt;Every tool and retrieval operation should carry tenant context.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tenant_id
user_id
roles
permissions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User
 ↓
Authentication
 ↓
Tenant Context
 ↓
Agent
 ↓
Tool
 ↓
Authorization
 ↓
Tenant-scoped Data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same principle applies to RAG.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Vector Search
 +
tenant_id filter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;should ensure that retrieved documents belong to the correct tenant.&lt;/p&gt;




&lt;h1&gt;
  
  
  AI Agent Guardrails
&lt;/h1&gt;

&lt;p&gt;Production agents should have explicit guardrails.&lt;/p&gt;

&lt;p&gt;Examples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Input validation
Output validation
Tool authorization
Rate limiting
Token limits
Iteration limits
PII protection
Audit logging
Human approval
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For high-risk actions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Agent
 ↓
Tool Request
 ↓
Risk Evaluation
 ↓
Human Approval
 ↓
Execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a human-in-the-loop architecture.&lt;/p&gt;




&lt;h1&gt;
  
  
  Human-in-the-Loop
&lt;/h1&gt;

&lt;p&gt;Not every decision should be fully automated.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Refund amount &amp;lt; $50
    ↓
Automatic

Refund amount &amp;gt; $50
    ↓
Human approval
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create support ticket
    ↓
Automatic

Delete account
    ↓
Confirmation required
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives us a practical balance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI Automation
+
Business Rules
+
Human Oversight
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  RAG + Tool Calling + Memory
&lt;/h1&gt;

&lt;p&gt;At this point, we can combine everything we've discussed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                         User
                          ↓
                     Spring Boot
                          ↓
                      ChatClient
                          ↓
                       AI Agent
                          ↓
              ┌───────────┼───────────┐
              ↓           ↓           ↓
            Memory       RAG         Tools
              ↓           ↓           ↓
          PostgreSQL   pgvector    REST APIs
                                      ↓
                               Business Services
                                      ↓
                                   Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a strong foundation for enterprise AI applications.&lt;/p&gt;




&lt;h1&gt;
  
  
  Example Enterprise Agent
&lt;/h1&gt;

&lt;p&gt;Imagine a sales assistant.&lt;/p&gt;

&lt;p&gt;The user asks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Show me the latest opportunities for Acme
and tell me which ones are likely to close this month.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent could:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. getCustomer("Acme")
2. getOpportunities("Acme")
3. retrieve sales documentation
4. analyze opportunity information
5. generate summary
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the user says:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create a follow-up task for the highest priority opportunity.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent can:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Identify opportunity
2. createFollowUpTask()
3. Return task details
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where AI starts becoming an application interface rather than simply a chatbot.&lt;/p&gt;




&lt;h1&gt;
  
  
  A Useful Mental Model
&lt;/h1&gt;

&lt;p&gt;Think about the responsibilities this way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LLM
=
Reasoning + Language

RAG
=
Knowledge Retrieval

Tools
=
Actions + Live Data

Memory
=
Conversation Context

Spring Boot
=
Application + Security + Business Logic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LLM
 +
RAG
 +
Tools
 +
Memory
 +
Business Logic
 =
AI Application
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  What Spring AI Gives Java Developers
&lt;/h1&gt;

&lt;p&gt;Spring AI provides abstractions that allow Java developers to work with AI capabilities using familiar Spring patterns.&lt;/p&gt;

&lt;p&gt;Important building blocks include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ChatClient
ChatModel
EmbeddingModel
VectorStore
Document
Advisors
Chat Memory
Tools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means an enterprise Java team can integrate AI into an existing Spring Boot architecture instead of creating an entirely separate AI stack.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Existing Spring Boot Application
              ↓
        Spring AI Layer
              ↓
      Model + RAG + Tools
              ↓
     Existing Microservices
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes AI integration much more practical for Java teams.&lt;/p&gt;




&lt;h1&gt;
  
  
  Production Architecture
&lt;/h1&gt;

&lt;p&gt;A more complete production system might eventually look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                         ┌───────────────┐
                         │     User      │
                         └───────┬───────┘
                                 ↓
                         API Gateway
                                 ↓
                         Authentication
                                 ↓
                         Spring Boot API
                                 ↓
                            AI Agent
                                 ↓
          ┌──────────────────────┼──────────────────────┐
          ↓                      ↓                      ↓
       Memory                  RAG                    Tools
          ↓                      ↓                      ↓
     PostgreSQL              pgvector              Microservices
                                                        ↓
                                                Business Database
                                 ↓
                           LLM Provider
                                 ↓
                              Response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And around the entire system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Security
Observability
Rate Limiting
Audit Logging
Guardrails
Evaluation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are not optional concerns in serious enterprise deployments.&lt;/p&gt;




&lt;h1&gt;
  
  
  Agent vs Chatbot
&lt;/h1&gt;

&lt;p&gt;It is useful to understand the difference.&lt;/p&gt;

&lt;h3&gt;
  
  
  Chatbot
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User
 ↓
LLM
 ↓
Answer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  RAG Chatbot
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User
 ↓
Retrieve Knowledge
 ↓
LLM
 ↓
Answer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Tool-Enabled Assistant
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User
 ↓
LLM
 ↓
Tool
 ↓
Result
 ↓
Answer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  AI Agent
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User
 ↓
Agent
 ↓
Reason
 ↓
Tool
 ↓
Observe
 ↓
Reason
 ↓
Tool
 ↓
Observe
 ↓
Final Answer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The complexity increases at every stage.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Agentic AI Stack
&lt;/h1&gt;

&lt;p&gt;We can now think about the evolution of an AI application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Level 1
LLM
 ↓
Text Generation

Level 2
LLM + RAG
 ↓
Knowledge Retrieval

Level 3
LLM + Tools
 ↓
Actions

Level 4
LLM + Tools + Memory
 ↓
Contextual Assistant

Level 5
LLM + RAG + Tools + Memory
 ↓
Agent

Level 6
Multiple Agents + Workflows
 ↓
Agentic System
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This progression is useful when deciding how much complexity your application actually needs.&lt;/p&gt;




&lt;h1&gt;
  
  
  Final Takeaway
&lt;/h1&gt;

&lt;p&gt;The evolution from traditional AI applications to agentic applications can be summarized as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LLM
 ↓
Generate Text

RAG
 ↓
Retrieve Knowledge

Tool Calling
 ↓
Take Actions

Memory
 ↓
Remember Context

Agents
 ↓
Make Decisions

Workflows
 ↓
Coordinate Multiple Steps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Spring AI provides Java developers with abstractions for building many of these capabilities inside the Spring ecosystem.&lt;/p&gt;

&lt;p&gt;The most important engineering principle is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Let the model decide, but let your application control.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The LLM can decide which tool may be useful.&lt;/p&gt;

&lt;p&gt;Your application should decide whether that tool is actually allowed to execute.&lt;/p&gt;

&lt;p&gt;That separation gives us a much safer architecture for enterprise AI.&lt;/p&gt;




&lt;h1&gt;
  
  
  What's Next?
&lt;/h1&gt;

&lt;p&gt;We've now covered three major capabilities:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LLM
 ↓
Generate

RAG
 ↓
Retrieve

Tools
 ↓
Act
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But there is another challenge.&lt;/p&gt;

&lt;p&gt;What happens when a system has:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Multiple agents
        ↓
Multiple tools
        ↓
Multiple services
        ↓
Multiple AI models
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How do these agents communicate?&lt;/p&gt;

&lt;p&gt;How do we standardize tool discovery?&lt;/p&gt;

&lt;p&gt;How can an AI agent securely interact with external tools and services?&lt;/p&gt;

&lt;p&gt;This leads us toward another important concept in modern AI engineering:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Model Context Protocol — MCP.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In the next article, we'll explore:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Building MCP Clients and Tool-Based AI Applications with Spring AI.&lt;/strong&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Key Takeaways
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Tool calling allows LLMs to interact with application capabilities.&lt;/li&gt;
&lt;li&gt;The LLM requests tools; your application executes them.&lt;/li&gt;
&lt;li&gt;Tools can expose APIs, databases, business operations, and services.&lt;/li&gt;
&lt;li&gt;Chat memory provides conversational context.&lt;/li&gt;
&lt;li&gt;RAG provides external knowledge.&lt;/li&gt;
&lt;li&gt;Agents can combine RAG, memory, and tools.&lt;/li&gt;
&lt;li&gt;Not every workflow requires an agent.&lt;/li&gt;
&lt;li&gt;Deterministic workflows are often better for predictable business processes.&lt;/li&gt;
&lt;li&gt;Tool permissions and authorization are critical.&lt;/li&gt;
&lt;li&gt;Destructive operations should require stronger controls.&lt;/li&gt;
&lt;li&gt;Multi-tenant applications must enforce tenant isolation at the tool and retrieval layers.&lt;/li&gt;
&lt;li&gt;Agent loops should have iteration, timeout, and tool-call limits.&lt;/li&gt;
&lt;li&gt;Observability is essential for debugging agent behavior.&lt;/li&gt;
&lt;li&gt;Human-in-the-loop approval is useful for high-risk actions.&lt;/li&gt;
&lt;li&gt;Spring AI provides abstractions that make these patterns accessible to Java and Spring Boot developers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The future of enterprise AI isn't just:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LLM → Answer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's increasingly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LLM
 ↓
Reason
 ↓
Retrieve
 ↓
Call Tools
 ↓
Observe
 ↓
Act
 ↓
Remember
 ↓
Complete the Goal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's where &lt;strong&gt;AI agents with Spring AI&lt;/strong&gt; become truly interesting.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>java</category>
      <category>springboot</category>
    </item>
    <item>
      <title>Building RAG Applications with Spring AI: Connect LLMs to Your Own Data</title>
      <dc:creator>Ayush Shrivastava</dc:creator>
      <pubDate>Mon, 31 Aug 2026 13:07:35 +0000</pubDate>
      <link>https://dev.to/ayshriv/building-rag-applications-with-spring-ai-connect-llms-to-your-own-data-4bkc</link>
      <guid>https://dev.to/ayshriv/building-rag-applications-with-spring-ai-connect-llms-to-your-own-data-4bkc</guid>
      <description>&lt;p&gt;Large Language Models are powerful.&lt;/p&gt;

&lt;p&gt;But there is one fundamental limitation:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;An LLM doesn't automatically know your application's private data.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Your company policies, product documentation, internal knowledge base, customer records, PDFs, technical documentation, or database content are not necessarily part of the model's training data.&lt;/p&gt;

&lt;p&gt;This is where &lt;strong&gt;RAG — Retrieval-Augmented Generation&lt;/strong&gt; comes in.&lt;/p&gt;

&lt;p&gt;Instead of asking an LLM to answer directly, we first retrieve relevant information from our own data and provide that information as context to the model.&lt;/p&gt;

&lt;p&gt;In this article, we'll build the foundation of a &lt;strong&gt;RAG application using Spring AI&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is RAG?
&lt;/h2&gt;

&lt;p&gt;RAG stands for:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Retrieval-Augmented Generation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The idea is simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User Question
      ↓
Retrieve Relevant Information
      ↓
Add Retrieved Context to Prompt
      ↓
LLM
      ↓
Generated Answer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example, imagine we have a company's internal documentation.&lt;/p&gt;

&lt;p&gt;A user asks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;What is our refund policy for annual subscriptions?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of expecting the LLM to magically know the answer, our application:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Searches the company's documents.&lt;/li&gt;
&lt;li&gt;Finds the relevant refund-policy content.&lt;/li&gt;
&lt;li&gt;Adds that content to the prompt.&lt;/li&gt;
&lt;li&gt;Sends the prompt to the LLM.&lt;/li&gt;
&lt;li&gt;Generates an answer grounded in the retrieved information.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is the core idea behind RAG.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Do We Need RAG?
&lt;/h1&gt;

&lt;p&gt;A normal LLM application looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User
  ↓
Application
  ↓
LLM
  ↓
Answer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The problem?&lt;/p&gt;

&lt;p&gt;The LLM only has access to the information available to it.&lt;/p&gt;

&lt;p&gt;With RAG, the architecture becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                  ┌──────────────┐
                  │  Documents   │
                  └──────┬───────┘
                         ↓
                    Chunking
                         ↓
                    Embeddings
                         ↓
                  Vector Database
                         ↑
                         │
User → Query → Similarity Search
                         ↓
                  Relevant Context
                         ↓
                        LLM
                         ↓
                      Answer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the model can work with information from our own knowledge base.&lt;/p&gt;




&lt;h1&gt;
  
  
  The RAG Pipeline
&lt;/h1&gt;

&lt;p&gt;A production RAG pipeline typically contains these stages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Documents
    ↓
Document Loading
    ↓
Chunking
    ↓
Embeddings
    ↓
Vector Database
    ↓
Similarity Search
    ↓
Relevant Context
    ↓
Prompt
    ↓
LLM
    ↓
Answer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's understand each step.&lt;/p&gt;




&lt;h1&gt;
  
  
  1. Document Loading
&lt;/h1&gt;

&lt;p&gt;First, we need to get our data into the application.&lt;/p&gt;

&lt;p&gt;The source could be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PDF files&lt;/li&gt;
&lt;li&gt;Markdown files&lt;/li&gt;
&lt;li&gt;HTML pages&lt;/li&gt;
&lt;li&gt;Word documents&lt;/li&gt;
&lt;li&gt;Database records&lt;/li&gt;
&lt;li&gt;APIs&lt;/li&gt;
&lt;li&gt;Knowledge bases&lt;/li&gt;
&lt;li&gt;Internal documentation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Spring AI provides abstractions for working with documents.&lt;/p&gt;

&lt;p&gt;A document can be represented using Spring AI's &lt;code&gt;Document&lt;/code&gt; abstraction.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Document&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Document&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"Spring Boot is a framework for building Java applications..."&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We now have content that can be processed by our RAG pipeline.&lt;/p&gt;




&lt;h1&gt;
  
  
  2. Chunking
&lt;/h1&gt;

&lt;p&gt;We shouldn't usually store an entire document as a single vector.&lt;/p&gt;

&lt;p&gt;Imagine a 100-page PDF.&lt;/p&gt;

&lt;p&gt;A user asks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;How do I configure authentication?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We don't want to retrieve the entire PDF.&lt;/p&gt;

&lt;p&gt;Instead, we split the document into smaller pieces called &lt;strong&gt;chunks&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Document
   ↓
Chunk 1
Chunk 2
Chunk 3
Chunk 4
...
Chunk 100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each chunk represents a smaller piece of knowledge.&lt;/p&gt;

&lt;p&gt;A simple example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Chunk 1:
Introduction to Spring Security

Chunk 2:
Configuring authentication

Chunk 3:
Creating users

Chunk 4:
JWT authentication

Chunk 5:
Role-based authorization
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now when the user asks about JWT authentication, we can retrieve the relevant chunk instead of the entire document.&lt;/p&gt;




&lt;h1&gt;
  
  
  3. Embeddings
&lt;/h1&gt;

&lt;p&gt;This is where things get interesting.&lt;/p&gt;

&lt;p&gt;A computer doesn't understand the semantic meaning of text in the same way humans do.&lt;/p&gt;

&lt;p&gt;We need a way to represent text numerically.&lt;/p&gt;

&lt;p&gt;That's where &lt;strong&gt;embeddings&lt;/strong&gt; come in.&lt;/p&gt;

&lt;p&gt;An embedding model converts text into a vector.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"How do I configure JWT authentication?"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;might become something conceptually like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[0.12, -0.42, 0.87, 0.31, ...]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The actual vector contains many dimensions.&lt;/p&gt;

&lt;p&gt;The important part is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Semantically similar text produces vectors that are relatively close together in vector space.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"How can I configure JWT?"
        ↓
[0.12, 0.81, 0.42, ...]

"JWT authentication configuration"
        ↓
[0.15, 0.78, 0.45, ...]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These vectors should have high similarity.&lt;/p&gt;




&lt;h1&gt;
  
  
  4. Vector Database
&lt;/h1&gt;

&lt;p&gt;Now we need somewhere to store these embeddings.&lt;/p&gt;

&lt;p&gt;That's where a &lt;strong&gt;vector database&lt;/strong&gt; comes in.&lt;/p&gt;

&lt;p&gt;Popular choices include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PostgreSQL + pgvector&lt;/li&gt;
&lt;li&gt;Pinecone&lt;/li&gt;
&lt;li&gt;Weaviate&lt;/li&gt;
&lt;li&gt;Qdrant&lt;/li&gt;
&lt;li&gt;Milvus&lt;/li&gt;
&lt;li&gt;Elasticsearch&lt;/li&gt;
&lt;li&gt;Redis&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a Spring Boot application, PostgreSQL with &lt;code&gt;pgvector&lt;/code&gt; is an especially interesting option because you can keep your relational data and vector data within the same ecosystem.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Document Chunk
      ↓
Embedding Model
      ↓
Vector
      ↓
Vector Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our database might contain something conceptually like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ID | Content                    | Embedding
---|----------------------------|----------------
1  | JWT configuration...       | [0.12,...]
2  | OAuth2 configuration...    | [0.42,...]
3  | Database configuration...  | [0.71,...]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  5. Similarity Search
&lt;/h1&gt;

&lt;p&gt;Now suppose the user asks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;How do I configure JWT authentication in Spring Boot?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We first generate an embedding for the question.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User Query
    ↓
Embedding Model
    ↓
Query Vector
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we search the vector database for similar vectors.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Query Vector
     ↓
Vector Database
     ↓
Similarity Search
     ↓
Top K Relevant Chunks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Result 1 → JWT configuration
Result 2 → Spring Security authentication
Result 3 → SecurityFilterChain configuration
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These results become our &lt;strong&gt;context&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  6. Building the Prompt
&lt;/h1&gt;

&lt;p&gt;Now we combine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User question&lt;/li&gt;
&lt;li&gt;Retrieved context&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Context:

Spring Security can be configured using SecurityFilterChain.
JWT authentication can be implemented using a custom
authentication filter...

Question:

How do I configure JWT authentication in Spring Boot?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The LLM receives this information and generates the answer.&lt;/p&gt;

&lt;p&gt;This is the &lt;strong&gt;Augmented&lt;/strong&gt; part of Retrieval-Augmented Generation.&lt;/p&gt;




&lt;h1&gt;
  
  
  7. Sending Context to the LLM
&lt;/h1&gt;

&lt;p&gt;The final flow looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User Question
      ↓
Embedding
      ↓
Vector Search
      ↓
Relevant Documents
      ↓
Prompt + Context
      ↓
LLM
      ↓
Answer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The LLM isn't searching the database itself.&lt;/p&gt;

&lt;p&gt;Our application retrieves the information first and provides it to the model.&lt;/p&gt;




&lt;h1&gt;
  
  
  Building RAG with Spring AI
&lt;/h1&gt;

&lt;p&gt;Now let's look at how Spring AI simplifies this architecture.&lt;/p&gt;

&lt;p&gt;A typical Spring AI RAG application contains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spring Boot
     │
     ├── Document Reader
     │
     ├── Text Splitter
     │
     ├── Embedding Model
     │
     ├── Vector Store
     │
     └── Chat Model
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact model and vector store can be swapped without rewriting the entire application.&lt;/p&gt;

&lt;p&gt;That's one of the strengths of Spring AI's abstraction-based approach.&lt;/p&gt;




&lt;h1&gt;
  
  
  Project Setup
&lt;/h1&gt;

&lt;p&gt;Let's create a Spring Boot project.&lt;/p&gt;

&lt;p&gt;We'll need Spring AI dependencies for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Chat model&lt;/li&gt;
&lt;li&gt;Embeddings&lt;/li&gt;
&lt;li&gt;Vector store&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, with Maven:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework.ai&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-ai-starter-model-bedrock-converse&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework.ai&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-ai-starter-vector-store-pgvector&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should use versions compatible with the Spring AI version used by your project.&lt;/p&gt;




&lt;h1&gt;
  
  
  Configuring the Model
&lt;/h1&gt;

&lt;p&gt;If we're using AWS Bedrock, our application needs AWS credentials and model configuration.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;spring&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ai&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;bedrock&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;aws&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;us-east-1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact configuration depends on the Spring AI version and Bedrock model you're using.&lt;/p&gt;

&lt;p&gt;For production environments, don't hard-code AWS credentials inside &lt;code&gt;application.yml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Environment Variables
        ↓
IAM Roles
        ↓
AWS Credentials Provider Chain
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whenever possible, prefer IAM roles over static credentials.&lt;/p&gt;




&lt;h1&gt;
  
  
  Creating a Vector Store
&lt;/h1&gt;

&lt;p&gt;For PostgreSQL + pgvector, the architecture looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spring Boot
     ↓
Spring AI VectorStore
     ↓
PostgreSQL
     ↓
pgvector
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The vector store becomes the bridge between our application and the vector database.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;VectorStore&lt;/span&gt; &lt;span class="n"&gt;vectorStore&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can then add documents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;vectorStore&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And later search:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Document&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="n"&gt;vectorStore&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;similaritySearch&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="nc"&gt;SearchRequest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;topK&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important abstraction here is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VectorStore
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our application doesn't need to manually implement vector similarity calculations.&lt;/p&gt;




&lt;h1&gt;
  
  
  Loading Documents
&lt;/h1&gt;

&lt;p&gt;Let's say we have a PDF containing product documentation.&lt;/p&gt;

&lt;p&gt;Spring AI provides document readers that can load different document formats.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;documents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We then split the documents into chunks.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;TokenTextSplitter&lt;/span&gt; &lt;span class="n"&gt;splitter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TokenTextSplitter&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Document&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="n"&gt;splitter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;apply&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact splitter and configuration should be chosen based on your document structure and model context window.&lt;/p&gt;




&lt;h1&gt;
  
  
  Creating Embeddings
&lt;/h1&gt;

&lt;p&gt;The embedding model converts every chunk into a vector.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Document Chunk
      ↓
Embedding Model
      ↓
Vector
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Spring AI handles this through the embedding/vector-store integration.&lt;/p&gt;

&lt;p&gt;Then we can store the documents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;vectorStore&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our ingestion pipeline is now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PDF
 ↓
Documents
 ↓
Chunks
 ↓
Embeddings
 ↓
Vector Store
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Retrieval
&lt;/h1&gt;

&lt;p&gt;Now let's handle the user's question.&lt;/p&gt;

&lt;p&gt;Suppose the user asks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;How does authentication work in our application?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We search the vector store:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Document&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="n"&gt;vectorStore&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;similaritySearch&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="nc"&gt;SearchRequest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;topK&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We now have the most relevant pieces of information.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Result 1
JWT authentication uses...

Result 2
The authentication filter...

Result 3
Security configuration...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Building the RAG Prompt
&lt;/h1&gt;

&lt;p&gt;We can now construct a prompt using the retrieved documents.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;Document:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;getText&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;collect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Collectors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;joining&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"\n\n"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""
        Answer the question using only the provided context.

        Context:
        %s

        Question:
        %s
        """&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;formatted&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then send it to the chat model.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;ChatResponse&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="n"&gt;chatModel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Prompt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we return:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getResult&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getOutput&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getText&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's a basic RAG implementation.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Complete Flow
&lt;/h1&gt;

&lt;p&gt;Putting everything together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 INGESTION PIPELINE

              ┌───────────────┐
              │   Documents   │
              └───────┬───────┘
                      ↓
                Text Splitting
                      ↓
                 Embeddings
                      ↓
               Vector Store
                      │
                      │
                      │
                      ▼
                 RETRIEVAL
                      ▲
                      │
               User Question
                      ↓
                  Embedding
                      ↓
               Similarity Search
                      ↓
                Top K Chunks
                      ↓
                   Context
                      ↓
                    Prompt
                      ↓
                     LLM
                      ↓
                   Answer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the basic architecture behind many modern knowledge-based AI applications.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Chunking Matters
&lt;/h1&gt;

&lt;p&gt;One of the most underestimated parts of RAG is chunking.&lt;/p&gt;

&lt;p&gt;Bad chunking can produce bad retrieval.&lt;/p&gt;

&lt;p&gt;Imagine this document:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spring Security

Authentication allows the application to verify
the identity of a user.

Authorization determines whether the authenticated
user has permission to access a resource.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we split this badly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Chunk 1:
Authentication allows...

Chunk 2:
the identity of a user. Authorization determines...

Chunk 3:
whether the authenticated user...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;we may destroy important semantic relationships.&lt;/p&gt;

&lt;p&gt;A better strategy is to preserve meaningful boundaries where possible.&lt;/p&gt;

&lt;p&gt;Depending on the data, you may experiment with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Chunk Size
Overlap
Sentence boundaries
Paragraph boundaries
Markdown headings
Semantic sections
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is no universal chunk size that works for every RAG application.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Top-K Matters
&lt;/h1&gt;

&lt;p&gt;When performing similarity search, we usually retrieve the top K results.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;topK&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;means:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Return the 5 most relevant chunks.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But bigger isn't always better.&lt;/p&gt;

&lt;p&gt;If we retrieve too little:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;K = 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;we may miss important context.&lt;/p&gt;

&lt;p&gt;If we retrieve too much:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;K = 50
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;we may introduce irrelevant information and increase token usage.&lt;/p&gt;

&lt;p&gt;A common approach is to start with a small value and evaluate retrieval quality.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;K = 3
K = 5
K = 10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then measure which configuration works best for your dataset.&lt;/p&gt;




&lt;h1&gt;
  
  
  RAG vs Fine-Tuning
&lt;/h1&gt;

&lt;p&gt;A common question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why not fine-tune the model instead?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;RAG and fine-tuning solve different problems.&lt;/p&gt;

&lt;h3&gt;
  
  
  RAG
&lt;/h3&gt;

&lt;p&gt;Best when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Knowledge changes frequently&lt;/li&gt;
&lt;li&gt;You have private documents&lt;/li&gt;
&lt;li&gt;You need citations or traceability&lt;/li&gt;
&lt;li&gt;You need to update knowledge without retraining&lt;/li&gt;
&lt;li&gt;You want to retrieve specific information&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Fine-Tuning
&lt;/h3&gt;

&lt;p&gt;Useful when you want to change:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Model behavior&lt;/li&gt;
&lt;li&gt;Output style&lt;/li&gt;
&lt;li&gt;Domain-specific patterns&lt;/li&gt;
&lt;li&gt;Task performance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A useful mental model is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RAG
→ Give the model the right information.

Fine-tuning
→ Change how the model behaves.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In many production systems, they can also be used together.&lt;/p&gt;




&lt;h1&gt;
  
  
  RAG Is More Than Vector Search
&lt;/h1&gt;

&lt;p&gt;A basic RAG pipeline is only the beginning.&lt;/p&gt;

&lt;p&gt;Production RAG systems often introduce additional stages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Query
 ↓
Query Transformation
 ↓
Hybrid Retrieval
 ↓
Metadata Filtering
 ↓
Vector Search
 ↓
Reranking
 ↓
Context Compression
 ↓
Prompt Construction
 ↓
LLM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You might eventually introduce techniques such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hybrid search&lt;/li&gt;
&lt;li&gt;Metadata filtering&lt;/li&gt;
&lt;li&gt;Reranking&lt;/li&gt;
&lt;li&gt;HyDE&lt;/li&gt;
&lt;li&gt;Query rewriting&lt;/li&gt;
&lt;li&gt;Self-RAG&lt;/li&gt;
&lt;li&gt;Corrective RAG&lt;/li&gt;
&lt;li&gt;Graph RAG&lt;/li&gt;
&lt;li&gt;Agentic RAG&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where RAG becomes an engineering discipline rather than simply "put documents into a vector database."&lt;/p&gt;




&lt;h1&gt;
  
  
  Production Considerations
&lt;/h1&gt;

&lt;p&gt;If you're building RAG for production, don't stop at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PDF → Vector DB → LLM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You also need to think about:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Document ingestion
&lt;/h3&gt;

&lt;p&gt;How frequently are documents updated?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;New document
     ↓
Process
     ↓
Chunk
     ↓
Embed
     ↓
Update Vector Store
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Metadata
&lt;/h3&gt;

&lt;p&gt;Store useful metadata alongside chunks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;document_id
source
page_number
tenant_id
created_at
updated_at
document_type
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This becomes extremely useful for filtering.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tenant_id = "company-123"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;can ensure that users only retrieve documents belonging to their tenant.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Access Control
&lt;/h3&gt;

&lt;p&gt;This is critical.&lt;/p&gt;

&lt;p&gt;A RAG system must not retrieve documents that the current user isn't authorized to access.&lt;/p&gt;

&lt;p&gt;Your retrieval layer should respect application permissions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User
 ↓
Authentication
 ↓
Authorization
 ↓
Metadata Filters
 ↓
Retrieval
 ↓
LLM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Never assume that because the LLM can't "see" a document directly, the document is secure.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Observability
&lt;/h3&gt;

&lt;p&gt;Track things such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Retrieval latency
Embedding latency
LLM latency
Token usage
Retrieved chunks
Similarity scores
Failure rate
Answer quality
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without observability, debugging RAG becomes extremely difficult.&lt;/p&gt;




&lt;h1&gt;
  
  
  A Simple Mental Model
&lt;/h1&gt;

&lt;p&gt;If you're new to RAG, remember this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Embedding Model
    =
    "Convert meaning into numbers"

Vector Database
    =
    "Store and search those meanings"

Retriever
    =
    "Find relevant information"

LLM
    =
    "Use that information to generate an answer"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Retrieve → Augment → Generate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's RAG.&lt;/p&gt;




&lt;h1&gt;
  
  
  Where Spring AI Fits
&lt;/h1&gt;

&lt;p&gt;Spring AI gives Java developers abstractions around many of these building blocks.&lt;/p&gt;

&lt;p&gt;Instead of manually wiring every AI provider and vector database integration, we can work with abstractions such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ChatModel
EmbeddingModel
VectorStore
Document
DocumentReader
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows us to focus more on the application architecture rather than provider-specific implementation details.&lt;/p&gt;

&lt;p&gt;And that's particularly useful when building enterprise Java applications where we may want to change:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AWS Bedrock
     ↓
Another Model Provider
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;without completely rewriting our application.&lt;/p&gt;




&lt;h1&gt;
  
  
  Final Architecture
&lt;/h1&gt;

&lt;p&gt;A production-oriented Spring AI RAG application can eventually look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    ┌───────────────────┐
                    │    User / App     │
                    └─────────┬─────────┘
                              ↓
                       Spring Boot API
                              ↓
                       Query Processing
                              ↓
                       Retrieval Layer
                              ↓
                    ┌───────────────────┐
                    │   Vector Store    │
                    │   PostgreSQL      │
                    │    + pgvector     │
                    └─────────┬─────────┘
                              ↓
                     Relevant Context
                              ↓
                      Prompt Assembly
                              ↓
                    ┌───────────────────┐
                    │    Spring AI     │
                    └─────────┬─────────┘
                              ↓
                    ┌───────────────────┐
                    │   AWS Bedrock    │
                    └─────────┬─────────┘
                              ↓
                           Answer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And separately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Documents
    ↓
Document Reader
    ↓
Chunking
    ↓
Embedding Model
    ↓
PostgreSQL + pgvector
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  What's Next?
&lt;/h1&gt;

&lt;p&gt;We started this series by exploring how Spring AI can connect Java applications with modern AI models.&lt;/p&gt;

&lt;p&gt;With AWS Bedrock, we can access powerful foundation models without managing the underlying model infrastructure.&lt;/p&gt;

&lt;p&gt;Now, with RAG, we can take another major step:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We can connect those models to our own data.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The journey now looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LLM
 ↓
Spring AI
 ↓
AWS Bedrock
 ↓
RAG
 ↓
Vector Database
 ↓
Our Own Data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But there is another important capability missing.&lt;/p&gt;

&lt;p&gt;What if we don't just want the model to &lt;strong&gt;answer questions&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;What if we want the model to &lt;strong&gt;take actions&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User
 ↓
AI Agent
 ↓
Decide what to do
 ↓
Call a Tool
 ↓
Execute Action
 ↓
Return Result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's where &lt;strong&gt;tool calling and AI agents&lt;/strong&gt; come in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next up: Building AI Agents with Spring AI — Tool Calling, Memory, and Autonomous Workflows.&lt;/strong&gt;&lt;/p&gt;




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

&lt;ul&gt;
&lt;li&gt;RAG connects LLMs to external knowledge.&lt;/li&gt;
&lt;li&gt;Documents are split into smaller chunks.&lt;/li&gt;
&lt;li&gt;Embeddings convert text into vectors.&lt;/li&gt;
&lt;li&gt;Vector databases store and retrieve those vectors.&lt;/li&gt;
&lt;li&gt;Similarity search finds relevant information.&lt;/li&gt;
&lt;li&gt;Retrieved context is added to the LLM prompt.&lt;/li&gt;
&lt;li&gt;Spring AI provides abstractions for building these pipelines.&lt;/li&gt;
&lt;li&gt;PostgreSQL + pgvector is a practical option for Java applications.&lt;/li&gt;
&lt;li&gt;Production RAG requires security, metadata filtering, observability, and evaluation.&lt;/li&gt;
&lt;li&gt;RAG is not just vector search — retrieval quality ultimately determines answer quality.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're building AI applications with Java and Spring Boot, &lt;strong&gt;RAG is one of the most important patterns to understand.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>rag</category>
      <category>java</category>
      <category>springboot</category>
    </item>
    <item>
      <title>AWS Bedrock with Spring AI: Moving from Local Models to the Cloud</title>
      <dc:creator>Ayush Shrivastava</dc:creator>
      <pubDate>Thu, 27 Aug 2026 06:55:35 +0000</pubDate>
      <link>https://dev.to/ayshriv/aws-bedrock-with-spring-ai-moving-from-local-models-to-the-cloud-54md</link>
      <guid>https://dev.to/ayshriv/aws-bedrock-with-spring-ai-moving-from-local-models-to-the-cloud-54md</guid>
      <description>&lt;p&gt;In the previous parts of this series, we explored how to build AI-powered applications with Spring AI and run models locally. Local inference is great for experimentation, development, privacy-focused use cases, and understanding how LLM applications work.&lt;/p&gt;

&lt;p&gt;But production AI systems often need more.&lt;/p&gt;

&lt;p&gt;They need access to powerful foundation models, managed infrastructure, scalability, reliability, and the ability to switch between models without managing GPU servers yourself.&lt;/p&gt;

&lt;p&gt;This is where &lt;strong&gt;AWS Bedrock&lt;/strong&gt; comes in.&lt;/p&gt;

&lt;p&gt;In this article, we'll explore how to integrate &lt;strong&gt;AWS Bedrock with Spring AI&lt;/strong&gt; and move from local AI inference to a managed cloud-based architecture.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is AWS Bedrock?
&lt;/h2&gt;

&lt;p&gt;AWS Bedrock is a fully managed AWS service that provides access to foundation models from multiple AI providers through AWS infrastructure.&lt;/p&gt;

&lt;p&gt;Instead of downloading and running a model locally, your application sends requests to AWS Bedrock.&lt;/p&gt;

&lt;p&gt;The architecture changes from this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spring Boot Application
        ↓
    Spring AI
        ↓
Local Model Runtime
(Ollama / Local LLM)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spring Boot Application
        ↓
    Spring AI
        ↓
 AWS Bedrock API
        ↓
 Foundation Model
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your application no longer needs to manage the underlying AI infrastructure.&lt;/p&gt;

&lt;p&gt;AWS handles the model hosting, scaling, availability, and infrastructure required to run inference.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Move from Local Models to AWS Bedrock?
&lt;/h1&gt;

&lt;p&gt;Local models are incredibly useful, but they come with limitations.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You may need powerful hardware.&lt;/li&gt;
&lt;li&gt;Large models require significant memory and GPU resources.&lt;/li&gt;
&lt;li&gt;Scaling inference for multiple users becomes difficult.&lt;/li&gt;
&lt;li&gt;You are responsible for hosting and managing the infrastructure.&lt;/li&gt;
&lt;li&gt;Production reliability becomes your responsibility.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AWS Bedrock solves many of these infrastructure challenges.&lt;/p&gt;

&lt;p&gt;With Bedrock, you can focus more on your application instead of managing model servers.&lt;/p&gt;

&lt;p&gt;Your architecture becomes more like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client
   ↓
Spring Boot API
   ↓
Spring AI
   ↓
AWS Bedrock
   ↓
Foundation Model
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes it easier to build cloud-native AI applications.&lt;/p&gt;




&lt;h1&gt;
  
  
  How Spring AI Fits Into the Architecture
&lt;/h1&gt;

&lt;p&gt;Spring AI provides abstractions for working with different AI providers.&lt;/p&gt;

&lt;p&gt;Your application interacts with high-level APIs such as &lt;code&gt;ChatClient&lt;/code&gt; instead of manually writing HTTP requests for every model provider.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Explain AWS Bedrock in simple terms"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application code can remain relatively similar even when the underlying AI provider changes.&lt;/p&gt;

&lt;p&gt;For example, you might start with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spring AI → Ollama → Local Model
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And later move to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spring AI → AWS Bedrock → Cloud Model
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This abstraction is one of the major advantages of using Spring AI.&lt;/p&gt;

&lt;p&gt;Your business logic should ideally depend on AI capabilities rather than being tightly coupled to a specific provider.&lt;/p&gt;




&lt;h1&gt;
  
  
  Setting Up AWS Bedrock
&lt;/h1&gt;

&lt;p&gt;Before your Spring Boot application can communicate with AWS Bedrock, you need access to AWS and the required permissions.&lt;/p&gt;

&lt;p&gt;At a high level, the process looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AWS Account
    ↓
Enable Model Access
    ↓
Configure IAM Permissions
    ↓
Configure AWS Region
    ↓
Spring Boot Application
    ↓
Spring AI + AWS Bedrock
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact models available can vary depending on your AWS region and account configuration.&lt;/p&gt;

&lt;p&gt;You also need appropriate AWS credentials and permissions for invoking models.&lt;/p&gt;

&lt;p&gt;For local development, credentials can be provided through the AWS credential provider chain, environment variables, profiles, or other supported AWS authentication mechanisms.&lt;/p&gt;

&lt;p&gt;A typical environment configuration might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;your-access-key&lt;/span&gt;
&lt;span class="py"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;your-secret-key&lt;/span&gt;
&lt;span class="py"&gt;AWS_REGION&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;your-region&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, in production, avoid hardcoding credentials in your application configuration.&lt;/p&gt;

&lt;p&gt;Prefer IAM roles and managed identity mechanisms whenever possible.&lt;/p&gt;




&lt;h1&gt;
  
  
  Adding the Spring AI Bedrock Dependency
&lt;/h1&gt;

&lt;p&gt;Spring AI provides integrations for AWS Bedrock models.&lt;/p&gt;

&lt;p&gt;The dependency you use depends on the specific Spring AI version and Bedrock model integration you want to work with.&lt;/p&gt;

&lt;p&gt;For example, your Maven configuration may include a Spring AI AWS Bedrock starter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework.ai&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-ai-starter-model-bedrock-converse&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Spring AI's dependency management should also be configured using the appropriate BOM for your version.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependencyManagement&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;dependencies&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework.ai&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-ai-bom&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;YOUR_SPRING_AI_VERSION&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;type&amp;gt;&lt;/span&gt;pom&lt;span class="nt"&gt;&amp;lt;/type&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;scope&amp;gt;&lt;/span&gt;import&lt;span class="nt"&gt;&amp;lt;/scope&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/dependencies&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependencyManagement&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important idea is that Spring Boot auto-configuration can create the required AI components once the correct dependencies and AWS configuration are available.&lt;/p&gt;




&lt;h1&gt;
  
  
  Configuring AWS Bedrock
&lt;/h1&gt;

&lt;p&gt;Your application configuration tells Spring AI how to connect to AWS Bedrock.&lt;/p&gt;

&lt;p&gt;A simplified configuration might look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;spring&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ai&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;bedrock&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;aws&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;us-east-1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depending on the Spring AI version and model integration, additional configuration may be required for the selected model, credentials, generation settings, or Bedrock Converse API.&lt;/p&gt;

&lt;p&gt;For example, you may configure values such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS region&lt;/li&gt;
&lt;li&gt;Model ID&lt;/li&gt;
&lt;li&gt;Temperature&lt;/li&gt;
&lt;li&gt;Maximum tokens&lt;/li&gt;
&lt;li&gt;Top P&lt;/li&gt;
&lt;li&gt;Credential provider settings&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;spring&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ai&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;chat&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bedrock-converse&lt;/span&gt;

    &lt;span class="na"&gt;bedrock&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;aws&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;us-east-1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact property names can vary between Spring AI releases, so always check the documentation for the version you are using.&lt;/p&gt;




&lt;h1&gt;
  
  
  Building a Chat API with Spring AI and Bedrock
&lt;/h1&gt;

&lt;p&gt;Once the integration is configured, we can expose a simple API.&lt;/p&gt;

&lt;p&gt;First, inject &lt;code&gt;ChatClient.Builder&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@RestController&lt;/span&gt;
&lt;span class="nd"&gt;@RequestMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/chat"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ChatController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ChatClient&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;ChatController&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;chatClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@GetMapping&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@RequestParam&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now a request such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /api/chat?message=Explain Spring AI
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;follows this flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP Request
     ↓
ChatController
     ↓
ChatClient
     ↓
Spring AI
     ↓
AWS Bedrock
     ↓
Foundation Model
     ↓
Generated Response
     ↓
Spring Boot API
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The controller does not need to know the low-level details of the Bedrock API.&lt;/p&gt;

&lt;p&gt;Spring AI handles the integration layer.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Big Architecture Change
&lt;/h1&gt;

&lt;p&gt;The most important change is not just replacing one dependency with another.&lt;/p&gt;

&lt;p&gt;The architecture itself changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Local Inference
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User
 ↓
Spring Boot
 ↓
Spring AI
 ↓
Ollama
 ↓
Local LLM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, you are responsible for the machine running the model.&lt;/p&gt;

&lt;p&gt;You need to think about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CPU or GPU resources&lt;/li&gt;
&lt;li&gt;RAM&lt;/li&gt;
&lt;li&gt;Model downloads&lt;/li&gt;
&lt;li&gt;Model startup time&lt;/li&gt;
&lt;li&gt;Concurrent requests&lt;/li&gt;
&lt;li&gt;Scaling&lt;/li&gt;
&lt;li&gt;Monitoring the inference server&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Cloud Inference
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User
 ↓
Load Balancer
 ↓
Spring Boot Application
 ↓
Spring AI
 ↓
AWS Bedrock
 ↓
Foundation Model
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the model infrastructure is managed separately from your application.&lt;/p&gt;

&lt;p&gt;Your Spring Boot application becomes a consumer of AI infrastructure rather than the host of the model itself.&lt;/p&gt;

&lt;p&gt;This separation can significantly simplify production architecture.&lt;/p&gt;




&lt;h1&gt;
  
  
  Choosing Between Different Foundation Models
&lt;/h1&gt;

&lt;p&gt;One major benefit of Bedrock is access to multiple foundation models.&lt;/p&gt;

&lt;p&gt;Different models can be better suited for different workloads.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Chat Application
        ↓
   General Model

Document Analysis
        ↓
 Long Context Model

Structured Extraction
        ↓
 Model with Strong
 Structured Output

Complex Reasoning
        ↓
 Higher Capability Model
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means model selection becomes an architectural decision.&lt;/p&gt;

&lt;p&gt;You should consider factors such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Response quality&lt;/li&gt;
&lt;li&gt;Latency&lt;/li&gt;
&lt;li&gt;Cost&lt;/li&gt;
&lt;li&gt;Context window&lt;/li&gt;
&lt;li&gt;Tool calling capabilities&lt;/li&gt;
&lt;li&gt;Structured output support&lt;/li&gt;
&lt;li&gt;Regional availability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The best model is not always the largest or most expensive one.&lt;/p&gt;

&lt;p&gt;A production system may even route different requests to different models.&lt;/p&gt;




&lt;h1&gt;
  
  
  Managing AI Configuration
&lt;/h1&gt;

&lt;p&gt;One common mistake is placing model-specific configuration throughout the application.&lt;/p&gt;

&lt;p&gt;Instead of this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"bedrock"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Bedrock logic&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ollama"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Ollama logic&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Try to keep provider-specific infrastructure separate from your application logic.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application Layer
       ↓
Spring AI Abstraction
       ↓
Provider Configuration
       ↓
AWS Bedrock / Ollama / Other Models
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes it easier to change providers later.&lt;/p&gt;

&lt;p&gt;Your application should ideally focus on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;What should the AI do?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;How does this specific provider's HTTP API work?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Authentication and Security
&lt;/h1&gt;

&lt;p&gt;When working with AWS Bedrock, authentication becomes part of your architecture.&lt;/p&gt;

&lt;p&gt;Your application needs permission to invoke models.&lt;/p&gt;

&lt;p&gt;For production environments, the preferred approach is generally to use AWS IAM roles instead of embedding access keys inside application properties.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spring Boot on AWS
       ↓
IAM Role
       ↓
Temporary Credentials
       ↓
AWS Bedrock
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is safer than:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;application.yml
       ↓
Hardcoded AWS Keys
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should also think about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Least-privilege IAM permissions&lt;/li&gt;
&lt;li&gt;Credential rotation&lt;/li&gt;
&lt;li&gt;Environment separation&lt;/li&gt;
&lt;li&gt;Request logging&lt;/li&gt;
&lt;li&gt;Sensitive prompt data&lt;/li&gt;
&lt;li&gt;PII protection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Moving AI to the cloud also means thinking carefully about what data leaves your application.&lt;/p&gt;




&lt;h1&gt;
  
  
  Cost Becomes Part of the Architecture
&lt;/h1&gt;

&lt;p&gt;With local models, the main cost is often infrastructure.&lt;/p&gt;

&lt;p&gt;You pay for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GPU
RAM
Compute
Storage
Servers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With managed inference, cost is often related to model usage.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application Request
        ↓
Input Tokens
        +
Output Tokens
        ↓
Inference Cost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means AI applications should treat token usage as an engineering concern.&lt;/p&gt;

&lt;p&gt;You may need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Token monitoring&lt;/li&gt;
&lt;li&gt;Request limits&lt;/li&gt;
&lt;li&gt;Model selection policies&lt;/li&gt;
&lt;li&gt;Budget controls&lt;/li&gt;
&lt;li&gt;Prompt optimization&lt;/li&gt;
&lt;li&gt;Caching&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, sending unnecessary context to a model can increase both latency and cost.&lt;/p&gt;

&lt;p&gt;A good AI architecture should therefore optimize the entire request pipeline.&lt;/p&gt;




&lt;h1&gt;
  
  
  Adding Observability
&lt;/h1&gt;

&lt;p&gt;Production AI systems need more than application logs.&lt;/p&gt;

&lt;p&gt;You may want to monitor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request
   ↓
Prompt Size
   ↓
Model
   ↓
Latency
   ↓
Token Usage
   ↓
Cost
   ↓
Response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This helps answer important questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which requests are slow?&lt;/li&gt;
&lt;li&gt;Which model is being used?&lt;/li&gt;
&lt;li&gt;How many tokens are being consumed?&lt;/li&gt;
&lt;li&gt;Which prompts are expensive?&lt;/li&gt;
&lt;li&gt;Are failures coming from the application or the AI provider?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As your AI application grows, observability becomes essential.&lt;/p&gt;




&lt;h1&gt;
  
  
  Local Development vs Production
&lt;/h1&gt;

&lt;p&gt;You don't necessarily need to abandon local models completely.&lt;/p&gt;

&lt;p&gt;A practical architecture could use different providers for different environments.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Development
Spring Boot
     ↓
   Ollama
     ↓
 Local Model
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Production
Spring Boot
     ↓
Spring AI
     ↓
AWS Bedrock
     ↓
Cloud Model
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives developers a fast and inexpensive local development workflow while allowing production to use managed cloud infrastructure.&lt;/p&gt;

&lt;p&gt;The important part is keeping your application architecture flexible enough to support both.&lt;/p&gt;




&lt;h1&gt;
  
  
  What's Next?
&lt;/h1&gt;

&lt;p&gt;AWS Bedrock gives us access to powerful foundation models without managing the underlying AI infrastructure ourselves.&lt;/p&gt;

&lt;p&gt;But moving AI into production introduces new challenges.&lt;/p&gt;

&lt;p&gt;How do we manage conversation history?&lt;/p&gt;

&lt;p&gt;How do we provide the model with our own documents?&lt;/p&gt;

&lt;p&gt;How do we build applications that can retrieve relevant information before generating a response?&lt;/p&gt;

&lt;p&gt;In &lt;strong&gt;Part 7&lt;/strong&gt;, we'll take the next step and explore &lt;strong&gt;RAG with Spring AI&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We'll look at how documents move through the RAG pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Documents
    ↓
Chunking
    ↓
Embeddings
    ↓
Vector Database
    ↓
Similarity Search
    ↓
Relevant Context
    ↓
LLM
    ↓
Answer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where AI applications start becoming truly connected to your own data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next up: Building RAG Applications with Spring AI.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>aws</category>
      <category>bedrock</category>
      <category>springboot</category>
    </item>
    <item>
      <title>Run AI Models Locally with Docker Model Runner and Spring AI</title>
      <dc:creator>Ayush Shrivastava</dc:creator>
      <pubDate>Sun, 23 Aug 2026 08:22:51 +0000</pubDate>
      <link>https://dev.to/ayshriv/run-ai-models-locally-with-docker-model-runner-and-spring-ai-2god</link>
      <guid>https://dev.to/ayshriv/run-ai-models-locally-with-docker-model-runner-and-spring-ai-2god</guid>
      <description>&lt;p&gt;Generative AI development doesn't always require a cloud-hosted model.&lt;/p&gt;

&lt;p&gt;If you're building Java applications with Spring AI, you can run AI models locally and connect them to your Spring Boot application without depending on external model APIs.&lt;/p&gt;

&lt;p&gt;One interesting option is &lt;strong&gt;Docker Model Runner&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In this article, we'll explore how Docker Model Runner works, why it is useful for Java developers, and how to connect it with Spring AI.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Docker Model Runner?
&lt;/h2&gt;

&lt;p&gt;Docker Model Runner allows you to run AI models locally using Docker.&lt;/p&gt;

&lt;p&gt;Instead of sending every prompt to a cloud provider such as OpenAI or AWS Bedrock, you can run supported models on your own machine.&lt;/p&gt;

&lt;p&gt;The architecture looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spring Boot Application
        |
        v
     Spring AI
        |
        v
   OpenAI-compatible API
        |
        v
 Docker Model Runner
        |
        v
     Local LLM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your Java application interacts with the model through an API, while Docker handles running the model locally.&lt;/p&gt;

&lt;p&gt;This gives developers a convenient way to experiment with LLM applications without immediately provisioning cloud infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Run Models Locally?
&lt;/h2&gt;

&lt;p&gt;There are several reasons you may want to run an LLM locally.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Development without API costs
&lt;/h3&gt;

&lt;p&gt;During development, you may send hundreds or thousands of prompts.&lt;/p&gt;

&lt;p&gt;Running a model locally can eliminate API charges during experimentation.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Privacy
&lt;/h3&gt;

&lt;p&gt;Your prompts and application data can remain on your machine instead of being sent to an external AI provider.&lt;/p&gt;

&lt;p&gt;This can be particularly useful when experimenting with sensitive or proprietary data.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Offline experimentation
&lt;/h3&gt;

&lt;p&gt;Once the model is available locally, you don't need an internet connection for every inference request.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Faster experimentation
&lt;/h3&gt;

&lt;p&gt;Developers can experiment with prompts, tool calling, RAG pipelines, and application logic without repeatedly configuring cloud credentials.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Same Spring AI programming model
&lt;/h3&gt;

&lt;p&gt;One of the biggest advantages is that your application can continue using Spring AI abstractions.&lt;/p&gt;

&lt;p&gt;Your business logic doesn't need to be tightly coupled to a specific model provider.&lt;/p&gt;




&lt;h1&gt;
  
  
  Docker Model Runner + Spring AI
&lt;/h1&gt;

&lt;p&gt;Spring AI provides abstractions such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;ChatModel&lt;/span&gt;
&lt;span class="nc"&gt;ChatClient&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means your application can interact with an LLM without having to directly implement provider-specific HTTP calls.&lt;/p&gt;

&lt;p&gt;The important idea is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application
     |
     v
 ChatClient
     |
     v
 ChatModel
     |
     v
 Model API
     |
     v
Local Model
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your local model exposes an OpenAI-compatible API, Spring AI can communicate with it using the appropriate OpenAI configuration.&lt;/p&gt;




&lt;h1&gt;
  
  
  Step 1: Install Docker
&lt;/h1&gt;

&lt;p&gt;First, make sure Docker Desktop is installed and running on your machine.&lt;/p&gt;

&lt;p&gt;Docker Model Runner is available through Docker's model functionality, depending on your Docker Desktop version and configuration.&lt;/p&gt;

&lt;p&gt;You can verify that Docker is available with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then make sure Docker Desktop is running.&lt;/p&gt;




&lt;h1&gt;
  
  
  Step 2: Enable Docker Model Runner
&lt;/h1&gt;

&lt;p&gt;Docker Desktop provides the model-running infrastructure required to run supported AI models locally.&lt;/p&gt;

&lt;p&gt;Once enabled, you can work with models directly through Docker.&lt;/p&gt;

&lt;p&gt;The exact commands and model availability can change as Docker's model ecosystem evolves, so check the current Docker documentation for the model you want to use.&lt;/p&gt;

&lt;p&gt;The important concept for our Spring AI application is that Docker Model Runner exposes an API endpoint that our application can communicate with.&lt;/p&gt;




&lt;h1&gt;
  
  
  Step 3: Create a Spring Boot Project
&lt;/h1&gt;

&lt;p&gt;Create a Spring Boot application with Spring AI.&lt;/p&gt;

&lt;p&gt;For Maven, add the Spring AI OpenAI starter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework.ai&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-ai-starter-model-openai&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reason we're using the OpenAI starter is not because we're calling OpenAI's cloud service.&lt;/p&gt;

&lt;p&gt;We're using the OpenAI-compatible API supported by the local model runtime.&lt;/p&gt;

&lt;p&gt;This is an important concept:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;An OpenAI-compatible API does not necessarily mean you're using OpenAI's infrastructure.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It simply means the API follows a compatible request/response format.&lt;/p&gt;




&lt;h1&gt;
  
  
  Step 4: Configure Spring AI
&lt;/h1&gt;

&lt;p&gt;In &lt;code&gt;application.properties&lt;/code&gt;, configure the OpenAI base URL to point to your local Docker Model Runner endpoint.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;spring.ai.openai.base-url&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;http://localhost:&amp;lt;model-runner-port&amp;gt;&lt;/span&gt;
&lt;span class="py"&gt;spring.ai.openai.api-key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;dummy&lt;/span&gt;
&lt;span class="py"&gt;spring.ai.openai.chat.options.model&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;your-local-model&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact endpoint and model name depend on your Docker Model Runner setup.&lt;/p&gt;

&lt;p&gt;The API key may not actually be required by the local runtime, but the Spring AI OpenAI client expects the configuration property, so a placeholder value can be used when appropriate.&lt;/p&gt;




&lt;h1&gt;
  
  
  Step 5: Create a ChatClient
&lt;/h1&gt;

&lt;p&gt;Now we can create a &lt;code&gt;ChatClient&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@RestController&lt;/span&gt;
&lt;span class="nd"&gt;@RequestMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/ai"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AIController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ChatClient&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;AIController&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;chatClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/chat"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@RequestParam&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now your Spring Boot application has a simple endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /ai/chat?message=Explain dependency injection in Spring
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The flow becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP Request
     |
     v
Spring Boot
     |
     v
ChatClient
     |
     v
Spring AI
     |
     v
Docker Model Runner
     |
     v
Local LLM
     |
     v
Generated Response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  ChatClient Hides Provider Complexity
&lt;/h1&gt;

&lt;p&gt;This is where Spring AI becomes useful.&lt;/p&gt;

&lt;p&gt;Your controller doesn't need to know whether the model is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OpenAI&lt;/li&gt;
&lt;li&gt;Azure OpenAI&lt;/li&gt;
&lt;li&gt;AWS Bedrock&lt;/li&gt;
&lt;li&gt;Ollama&lt;/li&gt;
&lt;li&gt;Docker Model Runner&lt;/li&gt;
&lt;li&gt;another compatible provider&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The application interacts with the Spring AI abstraction.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;chatClient&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Explain Spring Boot dependency injection"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application focuses on &lt;strong&gt;what it wants from the model&lt;/strong&gt;, rather than implementing the underlying model communication itself.&lt;/p&gt;




&lt;h1&gt;
  
  
  Local LLM vs Cloud LLM
&lt;/h1&gt;

&lt;p&gt;Running a model locally doesn't mean local models are always better.&lt;/p&gt;

&lt;p&gt;There are trade-offs.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Local Models&lt;/th&gt;
&lt;th&gt;Cloud Models&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Data stays locally&lt;/td&gt;
&lt;td&gt;Data sent to provider&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No per-request API cost&lt;/td&gt;
&lt;td&gt;Usually usage-based pricing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Requires local compute&lt;/td&gt;
&lt;td&gt;Provider handles infrastructure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Potentially slower&lt;/td&gt;
&lt;td&gt;Often faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Limited by local hardware&lt;/td&gt;
&lt;td&gt;Access to larger models&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Useful for development&lt;/td&gt;
&lt;td&gt;Useful for production workloads&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For example, a developer laptop may be perfectly capable of running a smaller model.&lt;/p&gt;

&lt;p&gt;But running a large frontier model locally may require significantly more memory and compute.&lt;/p&gt;

&lt;p&gt;So the right question isn't:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Local or cloud?"&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;"Which model deployment strategy fits this workload?"&lt;/strong&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Where Docker Model Runner Fits
&lt;/h1&gt;

&lt;p&gt;Docker Model Runner is particularly interesting for developers who already use Docker as part of their development workflow.&lt;/p&gt;

&lt;p&gt;You can think of it as bringing model execution closer to the rest of your local development environment.&lt;/p&gt;

&lt;p&gt;Instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spring Boot
     |
     +----&amp;gt; OpenAI
     |
     +----&amp;gt; AWS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you can have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Docker Development Environment
        |
        +---- Spring Boot
        |
        +---- Database
        |
        +---- Redis
        |
        +---- AI Model
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This can make local AI application development much easier to reproduce.&lt;/p&gt;




&lt;h1&gt;
  
  
  A More Realistic Architecture
&lt;/h1&gt;

&lt;p&gt;Once you move beyond a simple chatbot, the architecture becomes more interesting.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;             Spring Boot
                  |
             Spring AI
                  |
        +---------+---------+
        |                   |
    ChatClient          Embeddings
        |                   |
        v                   v
Docker Model Runner     Vector Store
        |
        v
     Local LLM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This architecture can support applications involving:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;RAG&lt;/li&gt;
&lt;li&gt;document processing&lt;/li&gt;
&lt;li&gt;semantic search&lt;/li&gt;
&lt;li&gt;AI assistants&lt;/li&gt;
&lt;li&gt;summarization&lt;/li&gt;
&lt;li&gt;classification&lt;/li&gt;
&lt;li&gt;question answering&lt;/li&gt;
&lt;li&gt;tool calling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And because the application uses Spring AI abstractions, you can change parts of the architecture later.&lt;/p&gt;




&lt;h1&gt;
  
  
  Local Development Is More Than Cost Saving
&lt;/h1&gt;

&lt;p&gt;One of the biggest benefits of local models is actually &lt;strong&gt;developer experimentation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Imagine you're building a RAG application.&lt;/p&gt;

&lt;p&gt;You need to test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Documents
    ↓
Chunking
    ↓
Embeddings
    ↓
Vector Store
    ↓
Retrieval
    ↓
Prompt
    ↓
LLM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During development, you may repeatedly modify prompts and retrieval strategies.&lt;/p&gt;

&lt;p&gt;Having a local model can make that experimentation cheaper and easier.&lt;/p&gt;

&lt;p&gt;Later, when you're ready for production, you can evaluate whether a managed model provider makes more sense.&lt;/p&gt;




&lt;h1&gt;
  
  
  What About Production?
&lt;/h1&gt;

&lt;p&gt;This is where architecture decisions become important.&lt;/p&gt;

&lt;p&gt;A local model running on a developer laptop is obviously different from a production AI infrastructure setup.&lt;/p&gt;

&lt;p&gt;For production, you need to think about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GPU availability&lt;/li&gt;
&lt;li&gt;model size&lt;/li&gt;
&lt;li&gt;concurrency&lt;/li&gt;
&lt;li&gt;latency&lt;/li&gt;
&lt;li&gt;autoscaling&lt;/li&gt;
&lt;li&gt;observability&lt;/li&gt;
&lt;li&gt;security&lt;/li&gt;
&lt;li&gt;model updates&lt;/li&gt;
&lt;li&gt;failure recovery&lt;/li&gt;
&lt;li&gt;cost&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For some workloads, a managed cloud model will be the better option.&lt;/p&gt;

&lt;p&gt;For others, self-hosted inference can make sense because of privacy, compliance, cost, or latency requirements.&lt;/p&gt;

&lt;p&gt;The important thing is that your application architecture should avoid unnecessary coupling to one model provider.&lt;/p&gt;




&lt;h1&gt;
  
  
  Spring AI Makes This Easier
&lt;/h1&gt;

&lt;p&gt;This is one of the reasons I like the Spring AI abstraction.&lt;/p&gt;

&lt;p&gt;Your business code can work at a higher level:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;ChatClient&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of manually implementing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP request
    ↓
Authentication
    ↓
JSON serialization
    ↓
Provider API
    ↓
Response parsing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Spring AI handles much of that integration layer.&lt;/p&gt;

&lt;p&gt;That allows Java developers to focus on building the actual AI application.&lt;/p&gt;




&lt;h1&gt;
  
  
  Final Thoughts
&lt;/h1&gt;

&lt;p&gt;Docker Model Runner gives Java developers another option for experimenting with generative AI locally.&lt;/p&gt;

&lt;p&gt;Combined with Spring AI, the architecture becomes relatively straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spring Boot
     ↓
Spring AI
     ↓
ChatClient
     ↓
OpenAI-Compatible API
     ↓
Docker Model Runner
     ↓
Local LLM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bigger lesson isn't simply how to run one particular model.&lt;/p&gt;

&lt;p&gt;It's understanding &lt;strong&gt;model portability&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Your application shouldn't necessarily care whether the underlying model is running locally or in the cloud.&lt;/p&gt;

&lt;p&gt;Spring AI gives you abstractions that help separate your application logic from the model provider.&lt;/p&gt;

&lt;p&gt;And that's an important foundation for building production-ready AI applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;In Part 6, we'll move from local inference to the cloud and explore &lt;strong&gt;AWS Bedrock with Spring AI&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We'll look at how Spring Boot applications can interact with foundation models available through AWS and how the architecture changes when AI inference moves from your local machine to a managed cloud platform.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>docker</category>
      <category>springboot</category>
      <category>mcp</category>
    </item>
    <item>
      <title>AI Governance Needs Proof, Not Just Logs</title>
      <dc:creator>Ayush Shrivastava</dc:creator>
      <pubDate>Fri, 21 Aug 2026 07:41:19 +0000</pubDate>
      <link>https://dev.to/ayshriv/ai-governance-needs-proof-not-just-logs-3elk</link>
      <guid>https://dev.to/ayshriv/ai-governance-needs-proof-not-just-logs-3elk</guid>
      <description>&lt;h1&gt;
  
  
  AI Governance Needs Proof, Not Just Logs
&lt;/h1&gt;

&lt;p&gt;AI systems are becoming more autonomous.&lt;/p&gt;

&lt;p&gt;Large language models are no longer limited to generating text. They can call tools, access enterprise systems, retrieve sensitive information, execute workflows, communicate with other agents, and make decisions that can directly affect business operations.&lt;/p&gt;

&lt;p&gt;As AI becomes more capable, organizations need more than visibility into what these systems are doing.&lt;/p&gt;

&lt;p&gt;They need &lt;strong&gt;proof&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Logging Is Not the Same as Governance
&lt;/h2&gt;

&lt;p&gt;Traditional application logging answers a basic question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What happened?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;An application writes an event to a log, and engineers can later inspect it.&lt;/p&gt;

&lt;p&gt;But AI systems introduce a more difficult governance challenge.&lt;/p&gt;

&lt;p&gt;What if the system being monitored can influence the records used to monitor it?&lt;/p&gt;

&lt;p&gt;What if an administrator, compromised service, or malicious actor can modify or delete those records?&lt;/p&gt;

&lt;p&gt;At that point, having logs does not necessarily mean having trustworthy evidence.&lt;/p&gt;

&lt;p&gt;This is why AI governance cannot stop at collecting logs.&lt;/p&gt;

&lt;p&gt;It needs to establish:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Integrity&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Accountability&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Traceability&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Authorization&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Auditability&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Building a Tamper-Evident Audit Trail
&lt;/h2&gt;

&lt;p&gt;A tamper-evident audit trail is designed so that unauthorized changes to historical records can be detected.&lt;/p&gt;

&lt;p&gt;For AI infrastructure, this can include events such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;LLM requests and responses&lt;/li&gt;
&lt;li&gt;Tool calls and tool results&lt;/li&gt;
&lt;li&gt;MCP interactions&lt;/li&gt;
&lt;li&gt;Agent-to-agent communication&lt;/li&gt;
&lt;li&gt;Authorization decisions&lt;/li&gt;
&lt;li&gt;Policy enforcement events&lt;/li&gt;
&lt;li&gt;Identity and access information&lt;/li&gt;
&lt;li&gt;Model and workflow execution&lt;/li&gt;
&lt;li&gt;Security and governance decisions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of treating these events as ordinary application logs, they become part of a governance record designed to provide reliable evidence of what happened.&lt;/p&gt;

&lt;p&gt;Cryptographic signatures and hash chaining can help establish relationships between records and make unauthorized modifications detectable.&lt;/p&gt;

&lt;p&gt;The objective isn't simply to prevent every possible change.&lt;/p&gt;

&lt;p&gt;The objective is to make &lt;strong&gt;silent manipulation of history difficult to hide&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Your Infrastructure. Your Keys. Your Audit Trail.
&lt;/h2&gt;

&lt;p&gt;This principle is at the heart of &lt;strong&gt;DVARA&lt;/strong&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Your infra. Your keys. Your audit trail.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;AI governance should not require organizations to give up control of their infrastructure, credentials, or sensitive governance data.&lt;/p&gt;

&lt;p&gt;Your AI systems may use multiple models, tools, MCP servers, and agents.&lt;/p&gt;

&lt;p&gt;But the organization operating those systems should still have control over the environment in which governance data is generated and stored.&lt;/p&gt;

&lt;p&gt;This becomes especially important for enterprises operating in regulated or security-sensitive environments.&lt;/p&gt;

&lt;p&gt;Organizations need to know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Who made the request?&lt;/li&gt;
&lt;li&gt;Which model processed it?&lt;/li&gt;
&lt;li&gt;Which tool was called?&lt;/li&gt;
&lt;li&gt;What authorization decision was made?&lt;/li&gt;
&lt;li&gt;What action did the agent take?&lt;/li&gt;
&lt;li&gt;What happened afterward?&lt;/li&gt;
&lt;li&gt;Can we prove that the record hasn't been silently rewritten?&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  AI Agents Make This Even More Important
&lt;/h2&gt;

&lt;p&gt;AI agents introduce another layer of complexity.&lt;/p&gt;

&lt;p&gt;A traditional application may follow a relatively predictable sequence of operations.&lt;/p&gt;

&lt;p&gt;An autonomous agent can:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Receive a request&lt;/li&gt;
&lt;li&gt;Reason about the task&lt;/li&gt;
&lt;li&gt;Select a tool&lt;/li&gt;
&lt;li&gt;Execute an action&lt;/li&gt;
&lt;li&gt;Observe the result&lt;/li&gt;
&lt;li&gt;Decide what to do next&lt;/li&gt;
&lt;li&gt;Call another tool&lt;/li&gt;
&lt;li&gt;Communicate with another agent&lt;/li&gt;
&lt;li&gt;Complete the workflow&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One user request could therefore produce dozens of internal events.&lt;/p&gt;

&lt;p&gt;When multiple agents interact with each other, the audit trail becomes even more important.&lt;/p&gt;

&lt;p&gt;You need to understand not only the final outcome, but also the chain of actions that produced it.&lt;/p&gt;

&lt;p&gt;Without reliable records, investigating an unexpected AI action can become extremely difficult.&lt;/p&gt;

&lt;p&gt;With a strong audit trail, organizations can reconstruct what happened and establish accountability across the workflow.&lt;/p&gt;




&lt;h2&gt;
  
  
  From Observability to Accountability
&lt;/h2&gt;

&lt;p&gt;Observability helps engineers understand systems.&lt;/p&gt;

&lt;p&gt;Governance goes one step further.&lt;/p&gt;

&lt;p&gt;It asks whether the organization can &lt;strong&gt;demonstrate that the system behaved according to defined policies and whether the evidence itself can be trusted&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That distinction matters.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Capability&lt;/th&gt;
&lt;th&gt;What it answers&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Metrics&lt;/td&gt;
&lt;td&gt;What is happening?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Logs&lt;/td&gt;
&lt;td&gt;What events occurred?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Traces&lt;/td&gt;
&lt;td&gt;How did the request flow?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Audit trails&lt;/td&gt;
&lt;td&gt;What happened historically?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tamper-evident audit trails&lt;/td&gt;
&lt;td&gt;Can we detect whether the historical record was altered?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For AI systems operating in production, that distinction is becoming increasingly important.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Question Auditors Will Eventually Ask
&lt;/h2&gt;

&lt;p&gt;Imagine an auditor asks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"What did your AI system do on this particular request?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You provide the logs.&lt;/p&gt;

&lt;p&gt;Then they ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"How do you know those logs weren't modified?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That second question changes everything.&lt;/p&gt;

&lt;p&gt;"Because we have logs" is no longer a sufficient answer.&lt;/p&gt;

&lt;p&gt;Modern AI governance needs evidence that can withstand scrutiny.&lt;/p&gt;

&lt;p&gt;It needs a chain of accountability from the original request through:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User Request
     ↓
Authorization
     ↓
LLM / Agent
     ↓
Tool / MCP Call
     ↓
Policy Decision
     ↓
Action
     ↓
Audit Record
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a stronger foundation for trustworthy AI operations.&lt;/p&gt;




&lt;h2&gt;
  
  
  Governance Needs Proof
&lt;/h2&gt;

&lt;p&gt;The future of AI isn't just about making models more capable.&lt;/p&gt;

&lt;p&gt;It's about making AI systems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Observable&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Controllable&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Auditable&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Secure&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Accountable&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As organizations deploy LLMs, MCP infrastructure, and autonomous agents at scale, trustworthy audit trails will become an essential part of the architecture.&lt;/p&gt;

&lt;p&gt;Because ultimately, governance isn't about having more logs.&lt;/p&gt;

&lt;p&gt;It's about being able to answer three simple questions:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What happened?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who or what caused it?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can you prove the record is trustworthy?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's the difference between simply monitoring AI and building AI systems that organizations can confidently govern.&lt;/p&gt;




&lt;h2&gt;
  
  
  The DVARA Principle
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Your infra. Your keys. Your audit trail.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Your AI infrastructure should remain under your control.&lt;/p&gt;

&lt;p&gt;Your governance data should remain within your security boundary.&lt;/p&gt;

&lt;p&gt;And your audit trail should provide evidence you can trust.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI governance needs more than visibility.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It needs proof.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Learn More
&lt;/h2&gt;

&lt;p&gt;Explore &lt;strong&gt;DVARA&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dvarahq.com/" rel="noopener noreferrer"&gt;https://dvarahq.com/&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Topics
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;AI Governance&lt;/code&gt; &lt;code&gt;AI Engineering&lt;/code&gt; &lt;code&gt;Artificial Intelligence&lt;/code&gt; &lt;code&gt;LLMOps&lt;/code&gt; &lt;code&gt;MCP&lt;/code&gt; &lt;code&gt;AI Agents&lt;/code&gt; &lt;code&gt;AI Security&lt;/code&gt; &lt;code&gt;Audit Trail&lt;/code&gt; &lt;code&gt;Data Governance&lt;/code&gt; &lt;code&gt;DevSecOps&lt;/code&gt; &lt;code&gt;Responsible AI&lt;/code&gt; &lt;code&gt;Enterprise AI&lt;/code&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>dvara</category>
      <category>governance</category>
      <category>mcp</category>
    </item>
    <item>
      <title>Run Local LLMs with Ollama and Spring AI</title>
      <dc:creator>Ayush Shrivastava</dc:creator>
      <pubDate>Thu, 20 Aug 2026 06:14:52 +0000</pubDate>
      <link>https://dev.to/ayshriv/run-local-llms-with-ollama-and-spring-ai-36l3</link>
      <guid>https://dev.to/ayshriv/run-local-llms-with-ollama-and-spring-ai-36l3</guid>
      <description>&lt;p&gt;In the previous parts, we connected Spring AI with cloud-based AI models.&lt;/p&gt;

&lt;p&gt;But there is one important question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if you don't want to send your data to an external AI provider?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What if you want to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run an LLM on your own machine&lt;/li&gt;
&lt;li&gt;Develop AI applications without API costs&lt;/li&gt;
&lt;li&gt;Work without an internet connection&lt;/li&gt;
&lt;li&gt;Keep sensitive company data private&lt;/li&gt;
&lt;li&gt;Experiment with different open-source models&lt;/li&gt;
&lt;li&gt;Build AI features locally before moving them to production&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where &lt;strong&gt;Ollama&lt;/strong&gt; becomes very useful.&lt;/p&gt;

&lt;p&gt;In this article, we will learn how to run a local LLM using Ollama and connect it with &lt;strong&gt;Spring AI&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We will build a simple real-world &lt;strong&gt;AI Customer Support Assistant&lt;/strong&gt; using Java, Spring Boot, Spring AI, and Ollama.&lt;/p&gt;




&lt;h2&gt;
  
  
  What We Are Building
&lt;/h2&gt;

&lt;p&gt;Our application will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                  User
                    |
                    | HTTP Request
                    v
          +---------------------+
          |   Spring Boot API   |
          +---------------------+
                    |
                    v
             +-------------+
             |  Spring AI  |
             |  ChatClient |
             +-------------+
                    |
                    v
               +--------+
               | Ollama |
               +--------+
                    |
                    v
              Local LLM
             (Llama/Qwen)
                    |
                    v
              AI Response
                    |
                    v
                  User
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part is that the LLM is running &lt;strong&gt;locally&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;There is no need to send every prompt to OpenAI, Anthropic, or another cloud provider.&lt;/p&gt;




&lt;h1&gt;
  
  
  1. What Is Ollama?
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://ollama.com/" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt; makes it easy to run open-source LLMs locally.&lt;/p&gt;

&lt;p&gt;Instead of calling a remote API like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spring Boot
     |
     v
OpenAI API
     |
     v
Cloud LLM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;we can run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spring Boot
     |
     v
Spring AI
     |
     v
Ollama
     |
     v
Local LLM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ollama can run models such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Llama&lt;/li&gt;
&lt;li&gt;Qwen&lt;/li&gt;
&lt;li&gt;Gemma&lt;/li&gt;
&lt;li&gt;Mistral&lt;/li&gt;
&lt;li&gt;DeepSeek&lt;/li&gt;
&lt;li&gt;and many other compatible models&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The exact models available change over time, so always check the Ollama model library before choosing one.&lt;/p&gt;




&lt;h1&gt;
  
  
  2. Why Run an LLM Locally?
&lt;/h1&gt;

&lt;p&gt;Imagine you are building an internal HR application.&lt;/p&gt;

&lt;p&gt;Employees may send questions such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;What is our maternity leave policy?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;What is the process for requesting annual leave?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You may not want internal company information leaving your infrastructure.&lt;/p&gt;

&lt;p&gt;A local LLM can help:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Employee
   |
   v
Spring Boot
   |
   v
RAG / Business Logic
   |
   v
Ollama
   |
   v
Local LLM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This can provide a useful privacy boundary.&lt;/p&gt;

&lt;p&gt;However, remember:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Running an LLM locally does not automatically make your application secure.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You still need proper authentication, authorization, logging, data protection, network security, and prompt/data controls.&lt;/p&gt;




&lt;h1&gt;
  
  
  3. Install Ollama
&lt;/h1&gt;

&lt;p&gt;First, install Ollama on your operating system.&lt;/p&gt;

&lt;p&gt;After installation, verify it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the command works, Ollama is installed.&lt;/p&gt;

&lt;p&gt;Now we need an LLM.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull llama3.2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama run llama3.2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can now talk to the model directly from your terminal.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; Explain Java interfaces in simple English.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model will generate a response locally.&lt;/p&gt;




&lt;h1&gt;
  
  
  4. How Ollama Works
&lt;/h1&gt;

&lt;p&gt;At a high level, the architecture is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;             Your Application
                    |
                    v
              Ollama API
                    |
                    v
             Model Runtime
                    |
                    v
              Local Model
                    |
                    v
                Response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ollama exposes an API that applications can communicate with.&lt;/p&gt;

&lt;p&gt;Spring AI can communicate with this API for us.&lt;/p&gt;

&lt;p&gt;That means we don't have to manually build HTTP requests to the Ollama API.&lt;/p&gt;




&lt;h1&gt;
  
  
  5. Create a Spring Boot Project
&lt;/h1&gt;

&lt;p&gt;Let's create a Spring Boot application.&lt;/p&gt;

&lt;p&gt;You can use Spring Initializr or your preferred IDE.&lt;/p&gt;

&lt;p&gt;Basic project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;local-ai-demo
│
├── src
│   └── main
│       ├── java
│       │   └── com.example.localai
│       │       └── LocalAiApplication.java
│       │
│       └── resources
│           └── application.yml
│
└── pom.xml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Spring Web&lt;/li&gt;
&lt;li&gt;Spring AI Ollama&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  6. Maven Dependency
&lt;/h1&gt;

&lt;p&gt;Add the Spring AI Ollama starter to your &lt;code&gt;pom.xml&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework.ai&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-ai-starter-model-ollama&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should use the Spring AI version compatible with your Spring Boot version.&lt;/p&gt;

&lt;p&gt;For production projects, avoid randomly mixing Spring Boot and Spring AI versions.&lt;/p&gt;




&lt;h1&gt;
  
  
  7. Configure Ollama
&lt;/h1&gt;

&lt;p&gt;Now configure the Ollama model.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;spring&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ai&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;ollama&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;base-url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://localhost:11434&lt;/span&gt;
      &lt;span class="na"&gt;chat&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;llama3.2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;localhost:11434
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the default Ollama API endpoint.&lt;/p&gt;

&lt;p&gt;Your architecture now becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spring Boot
     |
     | HTTP
     v
localhost:11434
     |
     v
Ollama
     |
     v
llama3.2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  8. Create the ChatClient
&lt;/h1&gt;

&lt;p&gt;Spring AI provides &lt;code&gt;ChatClient&lt;/code&gt;, which gives us a clean API for interacting with chat models.&lt;/p&gt;

&lt;p&gt;Create a configuration class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;com.example.localai.config&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.ai.chat.client.ChatClient&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.context.annotation.Bean&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.context.annotation.Configuration&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@Configuration&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AiConfig&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Bean&lt;/span&gt;
    &lt;span class="nc"&gt;ChatClient&lt;/span&gt; &lt;span class="nf"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it.&lt;/p&gt;

&lt;p&gt;Spring AI will use the configured Ollama chat model.&lt;/p&gt;




&lt;h1&gt;
  
  
  9. Create Our First AI Endpoint
&lt;/h1&gt;

&lt;p&gt;Now let's create a simple controller.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;com.example.localai.controller&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.ai.chat.client.ChatClient&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.web.bind.annotation.*&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@RestController&lt;/span&gt;
&lt;span class="nd"&gt;@RequestMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/ai"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AiController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ChatClient&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;AiController&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChatClient&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;chatClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/ask"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;ask&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@RequestParam&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start your Spring Boot application.&lt;/p&gt;

&lt;p&gt;Then call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /api/ai/ask?question=Explain Spring Boot in simple English
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The request flows like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP Request
     |
     v
AiController
     |
     v
ChatClient
     |
     v
Spring AI
     |
     v
Ollama
     |
     v
Local LLM
     |
     v
AI Response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the response comes back to the client.&lt;/p&gt;




&lt;h1&gt;
  
  
  10. The Real-World Example
&lt;/h1&gt;

&lt;p&gt;Let's make our application more useful.&lt;/p&gt;

&lt;p&gt;Imagine we are building an &lt;strong&gt;AI Customer Support Assistant&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A customer sends:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;My payment was deducted but my subscription is still inactive.
What should I do?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of simply passing the question to the model, we can provide a system instruction.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/support"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;support&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@RequestParam&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;system&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"""
                    You are a helpful customer support assistant.

                    Answer in simple English.
                    Do not invent company policies.
                    If you do not know something, clearly say that
                    you do not have enough information.
                    """&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the model has some context about its role.&lt;/p&gt;




&lt;h1&gt;
  
  
  11. System Prompt vs User Prompt
&lt;/h1&gt;

&lt;p&gt;Spring AI allows us to separate instructions from user input.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;system&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"""
                You are an AI customer support assistant.
                Keep answers short and easy to understand.
                Never make up information.
                """&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Think about it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;System Prompt
     |
     | "Who are you?"
     | "How should you behave?"
     |
     v
   LLM
     ^
     |
     | "What does the user want?"
     |
User Prompt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This separation becomes extremely useful when building production AI applications.&lt;/p&gt;




&lt;h1&gt;
  
  
  12. Create a Service Layer
&lt;/h1&gt;

&lt;p&gt;Putting everything inside the controller is not a good architecture.&lt;/p&gt;

&lt;p&gt;Instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Controller
    |
    v
Service
    |
    v
ChatClient
    |
    v
Ollama
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;com.example.localai.service&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.ai.chat.client.ChatClient&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.stereotype.Service&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@Service&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AiService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ChatClient&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;AiService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChatClient&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;chatClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;ask&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the controller becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;com.example.localai.controller&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.example.localai.service.AiService&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.web.bind.annotation.*&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@RestController&lt;/span&gt;
&lt;span class="nd"&gt;@RequestMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/ai"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AiController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;AiService&lt;/span&gt; &lt;span class="n"&gt;aiService&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;AiController&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AiService&lt;/span&gt; &lt;span class="n"&gt;aiService&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;aiService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aiService&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/ask"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;ask&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@RequestParam&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;aiService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ask&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This structure is much easier to extend later.&lt;/p&gt;




&lt;h1&gt;
  
  
  13. Building a Better Support Assistant
&lt;/h1&gt;

&lt;p&gt;Let's make the prompt more useful.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;support&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;system&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"""
                    You are a professional customer support assistant.

                    Rules:
                    1. Use simple English.
                    2. Be polite and helpful.
                    3. Give step-by-step instructions when possible.
                    4. Never invent policies.
                    5. If information is missing, ask for clarification.
                    """&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now a question like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I cannot reset my password.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;could produce something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I'm sorry you're having trouble resetting your password.

Please try these steps:

1. Open the login page.
2. Click "Forgot Password".
3. Enter your registered email.
4. Check your email for the reset link.

If you still cannot reset your password, contact support.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is already a useful AI feature.&lt;/p&gt;




&lt;h1&gt;
  
  
  14. Add Conversation Memory
&lt;/h1&gt;

&lt;p&gt;A basic AI call is stateless.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User:
My order is late.

AI:
Please provide your order number.

User:
It is 12345.

AI:
What order are you referring to?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model doesn't automatically know the previous conversation unless we provide that context.&lt;/p&gt;

&lt;p&gt;In a real application, we need conversation memory.&lt;/p&gt;

&lt;p&gt;The architecture becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User
 |
 v
Spring Boot
 |
 +------&amp;gt; Conversation Store
 |              |
 |              v
 |          Previous Messages
 |
 v
Spring AI
 |
 v
Ollama
 |
 v
LLM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depending on your application, the conversation history can be stored in databases such as PostgreSQL or Redis.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;conversation_id
        |
        v
+----------------------+
| User message         |
| AI response          |
| User message         |
| AI response          |
+----------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the relevant history can be included when making the next model call.&lt;/p&gt;

&lt;p&gt;This is an important step from a simple AI demo toward a production AI application.&lt;/p&gt;




&lt;h1&gt;
  
  
  15. Local LLM + RAG
&lt;/h1&gt;

&lt;p&gt;This is where local models become especially interesting.&lt;/p&gt;

&lt;p&gt;Suppose your company has:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Employee Handbook
Product Documentation
Customer FAQs
Internal Policies
Technical Documentation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can build a RAG system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;              Documents
                  |
                  v
             Text Parser
                  |
                  v
               Chunking
                  |
                  v
              Embeddings
                  |
                  v
            Vector Database
                  |
                  |
User Question ---&amp;gt; Retrieval
                  |
                  v
             Relevant Data
                  |
                  v
             Spring AI
                  |
                  v
               Ollama
                  |
                  v
             Local LLM
                  |
                  v
               Answer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the LLM doesn't need to know your company information beforehand.&lt;/p&gt;

&lt;p&gt;We retrieve the relevant information and provide it as context.&lt;/p&gt;




&lt;h1&gt;
  
  
  16. Example: Internal HR Assistant
&lt;/h1&gt;

&lt;p&gt;Imagine an employee asks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;How many days of annual leave can I take?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The system can search the company's HR documents.&lt;/p&gt;

&lt;p&gt;Suppose the vector database retrieves:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Employees receive 24 days of annual leave per calendar year.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Spring AI can then construct a prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Answer the question using only the following context.

Context:
Employees receive 24 days of annual leave per calendar year.

Question:
How many days of annual leave can I take?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The local LLM generates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;According to the company policy, employees receive
24 days of annual leave per calendar year.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The complete architecture becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    Employee
                       |
                       v
                Spring Boot API
                       |
                       v
                Spring AI RAG
                  /        \
                 /          \
                v            v
        Vector Database    Ollama
                |             |
                v             v
          Relevant Data     Local LLM
                 \            /
                  \          /
                   v        v
                    Answer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is much closer to a real enterprise AI architecture.&lt;/p&gt;




&lt;h1&gt;
  
  
  17. Why This Architecture Is Powerful
&lt;/h1&gt;

&lt;p&gt;Imagine an organization has sensitive documents.&lt;/p&gt;

&lt;p&gt;With a cloud-only architecture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application
     |
     v
Cloud AI API
     |
     v
External Model
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With a local architecture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application
     |
     v
Internal Infrastructure
     |
     +------&amp;gt; Vector DB
     |
     +------&amp;gt; Ollama
                |
                v
             Local LLM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This can be attractive for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Internal knowledge assistants&lt;/li&gt;
&lt;li&gt;Developer tools&lt;/li&gt;
&lt;li&gt;Private document analysis&lt;/li&gt;
&lt;li&gt;Customer support prototypes&lt;/li&gt;
&lt;li&gt;Offline applications&lt;/li&gt;
&lt;li&gt;Sensitive enterprise workloads&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But again, local inference is not a complete security strategy by itself.&lt;/p&gt;




&lt;h1&gt;
  
  
  18. Streaming AI Responses
&lt;/h1&gt;

&lt;p&gt;For chat applications, waiting for the entire response can feel slow.&lt;/p&gt;

&lt;p&gt;A better experience is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI is typing...

Hello
Hello, how
Hello, how can
Hello, how can I
Hello, how can I help?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Spring AI supports streaming responses through &lt;code&gt;Flux&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/stream"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;produces&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"text/event-stream"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Flux&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@RequestParam&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The client can receive pieces of the response as they are generated.&lt;/p&gt;

&lt;p&gt;This is useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI chat applications&lt;/li&gt;
&lt;li&gt;Coding assistants&lt;/li&gt;
&lt;li&gt;Customer support&lt;/li&gt;
&lt;li&gt;AI search&lt;/li&gt;
&lt;li&gt;Writing assistants&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  19. Model Selection Matters
&lt;/h1&gt;

&lt;p&gt;Not every local model is good for every task.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Small model
    |
    +-- Faster
    +-- Less memory
    +-- Lower hardware requirements

Large model
    |
    +-- Better reasoning potential
    +-- More memory
    +-- Higher latency
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your choice depends on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;RAM&lt;/li&gt;
&lt;li&gt;GPU/VRAM&lt;/li&gt;
&lt;li&gt;CPU&lt;/li&gt;
&lt;li&gt;Model size&lt;/li&gt;
&lt;li&gt;Context length&lt;/li&gt;
&lt;li&gt;Response speed&lt;/li&gt;
&lt;li&gt;Task complexity&lt;/li&gt;
&lt;li&gt;Accuracy requirements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a simple local experiment, start with a relatively small model.&lt;/p&gt;

&lt;p&gt;Then benchmark larger models if your hardware allows it.&lt;/p&gt;




&lt;h1&gt;
  
  
  20. Ollama vs Cloud LLMs
&lt;/h1&gt;

&lt;p&gt;A simple comparison:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Local Ollama&lt;/th&gt;
&lt;th&gt;Cloud LLM&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Internet required&lt;/td&gt;
&lt;td&gt;Usually no&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API cost&lt;/td&gt;
&lt;td&gt;No per-token cloud fee&lt;/td&gt;
&lt;td&gt;Usually usage-based&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data leaves machine&lt;/td&gt;
&lt;td&gt;Can stay local&lt;/td&gt;
&lt;td&gt;Sent to provider&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hardware required&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Mostly no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scaling&lt;/td&gt;
&lt;td&gt;Your responsibility&lt;/td&gt;
&lt;td&gt;Provider handles infrastructure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model choice&lt;/td&gt;
&lt;td&gt;Open/local models&lt;/td&gt;
&lt;td&gt;Provider-specific models&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Setup&lt;/td&gt;
&lt;td&gt;More infrastructure&lt;/td&gt;
&lt;td&gt;Usually easier&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Latency&lt;/td&gt;
&lt;td&gt;Depends on hardware&lt;/td&gt;
&lt;td&gt;Depends on network/provider&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;There is no universal winner.&lt;/p&gt;

&lt;p&gt;A practical architecture may even use both.&lt;/p&gt;




&lt;h1&gt;
  
  
  21. Hybrid AI Architecture
&lt;/h1&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 AI Gateway
                     |
            +--------+--------+
            |                 |
            v                 v
        Ollama            Cloud LLM
            |                 |
            v                 v
       Local Model       External Model
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You could use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sensitive requests
        |
        v
      Ollama
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Complex reasoning
        |
        v
    Cloud Model
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The routing decision can be implemented inside your application.&lt;/p&gt;

&lt;p&gt;This gives you more flexibility.&lt;/p&gt;




&lt;h1&gt;
  
  
  22. Production Considerations
&lt;/h1&gt;

&lt;p&gt;Running Ollama on your laptop is great for development.&lt;/p&gt;

&lt;p&gt;Production is different.&lt;/p&gt;

&lt;p&gt;You need to think about:&lt;/p&gt;

&lt;h3&gt;
  
  
  Infrastructure
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Load Balancer
      |
      v
Spring Boot Instances
      |
      v
AI Service
      |
      v
GPU-enabled inference servers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Observability
&lt;/h3&gt;

&lt;p&gt;Track:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Request latency&lt;/li&gt;
&lt;li&gt;Model latency&lt;/li&gt;
&lt;li&gt;Token usage where available&lt;/li&gt;
&lt;li&gt;Error rate&lt;/li&gt;
&lt;li&gt;Timeout rate&lt;/li&gt;
&lt;li&gt;Model failures&lt;/li&gt;
&lt;li&gt;Concurrent requests&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Security
&lt;/h3&gt;

&lt;p&gt;Protect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ollama endpoints&lt;/li&gt;
&lt;li&gt;Internal APIs&lt;/li&gt;
&lt;li&gt;User prompts&lt;/li&gt;
&lt;li&gt;Retrieved documents&lt;/li&gt;
&lt;li&gt;Conversation history&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not expose an unauthenticated Ollama service directly to the public internet.&lt;/p&gt;




&lt;h1&gt;
  
  
  23. Handling Errors
&lt;/h1&gt;

&lt;p&gt;AI services can fail.&lt;/p&gt;

&lt;p&gt;Your application should not assume every model call succeeds.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;ask&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;RuntimeException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"AI service is currently unavailable"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In a production application, use a proper exception hierarchy and global exception handling rather than exposing raw exceptions.&lt;/p&gt;

&lt;p&gt;You may also add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Timeout
Retry
Circuit Breaker
Fallback
Rate Limiting
Observability
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  24. A Better Production Architecture
&lt;/h1&gt;

&lt;p&gt;A more realistic architecture could look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    Client
                      |
                      v
               API Gateway
                      |
                      v
              Spring Boot API
                      |
          +-----------+-----------+
          |                       |
          v                       v
     Conversation             RAG Service
       Service                    |
          |                       v
          |                 Vector Database
          |                       |
          +-----------+-----------+
                      |
                      v
                 AI Service
                      |
             +--------+--------+
             |                 |
             v                 v
          Ollama           Cloud LLM
             |                 |
             v                 v
        Local Model      External Model
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This architecture allows you to evolve from a simple local experiment into a production-grade AI platform.&lt;/p&gt;




&lt;h1&gt;
  
  
  25. Complete Minimal Example
&lt;/h1&gt;

&lt;p&gt;Here is the complete service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Service&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AiService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ChatClient&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;AiService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChatClient&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;chatClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;ask&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;system&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"""
                        You are a helpful AI assistant.
                        Answer in simple English.
                        Do not invent facts.
                        """&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Controller:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@RestController&lt;/span&gt;
&lt;span class="nd"&gt;@RequestMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/ai"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AiController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;AiService&lt;/span&gt; &lt;span class="n"&gt;aiService&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;AiController&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AiService&lt;/span&gt; &lt;span class="n"&gt;aiService&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;aiService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aiService&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/ask"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;ask&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@RequestParam&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;aiService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ask&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;spring&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ai&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;ollama&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;base-url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://localhost:11434&lt;/span&gt;
      &lt;span class="na"&gt;chat&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;llama3.2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Architecture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP Client
    |
    v
AiController
    |
    v
AiService
    |
    v
ChatClient
    |
    v
Spring AI
    |
    v
Ollama
    |
    v
Local LLM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's enough to build your first local AI backend.&lt;/p&gt;




&lt;h1&gt;
  
  
  26. Test It
&lt;/h1&gt;

&lt;p&gt;Start Ollama:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama run llama3.2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then start Spring Boot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./mvnw spring-boot:run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Windows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mvnw.cmd spring-boot:run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET http://localhost:8080/api/ai/ask?question=Explain dependency injection in Spring Boot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should receive an answer generated by your local model.&lt;/p&gt;




&lt;h1&gt;
  
  
  27. What We Learned
&lt;/h1&gt;

&lt;p&gt;In this article, we built a local AI backend using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Java
   +
Spring Boot
   +
Spring AI
   +
Ollama
   +
Local LLM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We learned:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What Ollama is&lt;/li&gt;
&lt;li&gt;How to install and run a local model&lt;/li&gt;
&lt;li&gt;How Spring AI connects to Ollama&lt;/li&gt;
&lt;li&gt;How to use &lt;code&gt;ChatClient&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;How to create an AI REST API&lt;/li&gt;
&lt;li&gt;How to use system and user prompts&lt;/li&gt;
&lt;li&gt;How streaming works&lt;/li&gt;
&lt;li&gt;How local LLMs can be used with RAG&lt;/li&gt;
&lt;li&gt;How to think about production architecture&lt;/li&gt;
&lt;li&gt;When local and cloud models can be combined&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Final Thoughts
&lt;/h1&gt;

&lt;p&gt;Running an LLM locally changes the way we think about AI applications.&lt;/p&gt;

&lt;p&gt;You don't always need to start with an expensive cloud API.&lt;/p&gt;

&lt;p&gt;You can start on your own laptop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ollama
   |
Local LLM
   |
Spring AI
   |
Spring Boot
   |
REST API
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then gradually evolve it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Local LLM
    ↓
RAG
    ↓
Vector Database
    ↓
Conversation Memory
    ↓
Tool Calling
    ↓
AI Agents
    ↓
Observability
    ↓
Production AI Platform
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And this is where &lt;strong&gt;Spring AI becomes interesting for Java developers&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You can use the Spring ecosystem you already know and add modern AI capabilities without completely changing how you build backend applications.&lt;/p&gt;

&lt;p&gt;In the next part, we can go one step further and build a &lt;strong&gt;RAG application with Spring AI, embeddings, PostgreSQL + pgvector, and a local Ollama model&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That is where our simple chatbot starts becoming a real AI application.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>pgaichallenge</category>
      <category>springboot</category>
      <category>java</category>
    </item>
    <item>
      <title>Build Your First Spring AI Application with OpenAI Using Spring Boot</title>
      <dc:creator>Ayush Shrivastava</dc:creator>
      <pubDate>Mon, 17 Aug 2026 19:44:38 +0000</pubDate>
      <link>https://dev.to/ayshriv/build-your-first-spring-ai-application-with-openai-using-spring-boot-c3b</link>
      <guid>https://dev.to/ayshriv/build-your-first-spring-ai-application-with-openai-using-spring-boot-c3b</guid>
      <description>&lt;h1&gt;
  
  
  Build Your First Spring AI Application with OpenAI Using Spring Boot
&lt;/h1&gt;

&lt;p&gt;If you have been following this Spring AI series, you already understand the two most important abstractions: &lt;code&gt;ChatClient&lt;/code&gt; and &lt;code&gt;ChatModel&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In the previous article, we learned that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your Java Code
      |
      v
 ChatClient
      |
      v
 ChatModel
      |
      v
 AI Provider
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ChatClient&lt;/code&gt; gives us a developer-friendly API, while &lt;code&gt;ChatModel&lt;/code&gt; handles the underlying model integration.&lt;/p&gt;

&lt;p&gt;Now it is time to build something real.&lt;/p&gt;

&lt;p&gt;In this article, we will build our first &lt;strong&gt;Spring AI application using OpenAI&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We will start from project setup and configuration, connect Spring Boot to OpenAI, create a REST API, send prompts to an AI model, and understand what happens behind the scenes.&lt;/p&gt;

&lt;p&gt;By the end, you will have a working AI-powered Spring Boot API.&lt;/p&gt;




&lt;h2&gt;
  
  
  What We Are Building
&lt;/h2&gt;

&lt;p&gt;Our application will expose a simple REST endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /api/chat?message=Explain dependency injection
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The flow will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client
   |
   v
Spring Boot REST API
   |
   v
ChatClient
   |
   v
ChatModel
   |
   v
OpenAI
   |
   v
AI Response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The goal is intentionally simple.&lt;/p&gt;

&lt;p&gt;We want to understand the complete flow before adding more advanced concepts such as RAG, tools, memory, structured output, and AI agents.&lt;/p&gt;




&lt;h1&gt;
  
  
  Prerequisites
&lt;/h1&gt;

&lt;p&gt;Before starting, you should have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Java installed&lt;/li&gt;
&lt;li&gt;Maven installed&lt;/li&gt;
&lt;li&gt;A Spring Boot project&lt;/li&gt;
&lt;li&gt;An OpenAI API key&lt;/li&gt;
&lt;li&gt;Basic knowledge of Spring Boot&lt;/li&gt;
&lt;li&gt;Basic understanding of REST APIs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You should also be comfortable with dependency injection and creating REST controllers.&lt;/p&gt;

&lt;p&gt;If you have followed the previous articles in this series, most of this should already be familiar.&lt;/p&gt;




&lt;h1&gt;
  
  
  Step 1: Create a Spring Boot Project
&lt;/h1&gt;

&lt;p&gt;The easiest way to create the project is through Spring Initializr.&lt;/p&gt;

&lt;p&gt;Choose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Project: Maven

Language: Java

Spring Boot: Your compatible Spring Boot version

Packaging: Jar

Java: 17 or later
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For dependencies, we need Spring Web and the Spring AI OpenAI starter.&lt;/p&gt;

&lt;p&gt;Your project will eventually look something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spring-ai-openai-demo
│
├── src
│   ├── main
│   │   ├── java
│   │   │   └── com.example.demo
│   │   │       └── DemoApplication.java
│   │   │
│   │   └── resources
│   │       └── application.properties
│
└── pom.xml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Step 2: Add the Spring AI OpenAI Dependency
&lt;/h1&gt;

&lt;p&gt;Add the Spring AI OpenAI model starter to your Maven configuration.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework.ai&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-ai-starter-model-openai&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will also need Spring Web:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Your project now has the components required to build a simple AI REST API.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spring Boot
     |
     +-- Spring Web
     |
     +-- Spring AI
             |
             +-- OpenAI integration
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Step 3: Configure Your OpenAI API Key
&lt;/h1&gt;

&lt;p&gt;Spring AI needs credentials to communicate with OpenAI.&lt;/p&gt;

&lt;p&gt;You can configure the API key using an environment variable.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-api-key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Windows PowerShell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-api-key"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then reference it from your Spring configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;spring.ai.openai.api-key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;${OPENAI_API_KEY}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part is that your API key should &lt;strong&gt;not be hard-coded inside your Java source code&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Avoid doing this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;apiKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"sk-xxxxxxxx"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead, keep secrets outside your source code.&lt;/p&gt;

&lt;p&gt;A better approach is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Environment Variable
        |
        v
Spring Configuration
        |
        v
Spring AI
        |
        v
OpenAI
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This becomes even more important when deploying your application to production.&lt;/p&gt;




&lt;h1&gt;
  
  
  Step 4: Configure the Chat Model
&lt;/h1&gt;

&lt;p&gt;Spring AI needs to know which OpenAI chat model your application should use.&lt;/p&gt;

&lt;p&gt;You can configure the model through your application properties.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;spring.ai.openai.chat.options.model&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;your-model&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact model you choose depends on the models available to your OpenAI account and the requirements of your application.&lt;/p&gt;

&lt;p&gt;The important concept is that your application does not need to manually construct OpenAI HTTP requests.&lt;/p&gt;

&lt;p&gt;Spring AI handles that integration.&lt;/p&gt;




&lt;h1&gt;
  
  
  Step 5: Create the ChatController
&lt;/h1&gt;

&lt;p&gt;Now let's create our first AI-powered controller.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@RestController&lt;/span&gt;
&lt;span class="nd"&gt;@RequestMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ChatController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ChatClient&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;ChatController&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt; &lt;span class="n"&gt;chatClientBuilder&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;chatClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chatClientBuilder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/chat"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@RequestParam&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"message"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's understand this carefully.&lt;/p&gt;




&lt;h1&gt;
  
  
  Understanding ChatClient Injection
&lt;/h1&gt;

&lt;p&gt;The constructor receives:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;ChatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt; &lt;span class="n"&gt;chatClientBuilder&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Spring AI provides this builder through Spring Boot auto-configuration when the appropriate model integration is configured.&lt;/p&gt;

&lt;p&gt;We then create our &lt;code&gt;ChatClient&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;chatClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chatClientBuilder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the controller has a ready-to-use AI client.&lt;/p&gt;

&lt;p&gt;The architecture looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spring Boot
     |
     v
ChatClient.Builder
     |
     | build()
     v
ChatClient
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the same concept we explored in Part 2 of this series.&lt;/p&gt;




&lt;h1&gt;
  
  
  Step 6: Send Your First Prompt
&lt;/h1&gt;

&lt;p&gt;Now let's call the API.&lt;/p&gt;

&lt;p&gt;Start your Spring Boot application.&lt;/p&gt;

&lt;p&gt;Then send:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /api/chat?message=What is dependency injection in Spring?
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The controller receives:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;What is dependency injection in Spring?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and passes it to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;chatClient&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The flow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP Request
     |
     v
ChatController
     |
     v
ChatClient
     |
     v
ChatModel
     |
     v
OpenAI
     |
     v
AI Response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The generated response is returned to the client.&lt;/p&gt;

&lt;p&gt;That's it.&lt;/p&gt;

&lt;p&gt;You have now built a Spring Boot application that can communicate with an AI model.&lt;/p&gt;




&lt;h1&gt;
  
  
  Breaking Down the ChatClient API
&lt;/h1&gt;

&lt;p&gt;Let's look at this code again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;chatClient&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are three important operations here.&lt;/p&gt;

&lt;h2&gt;
  
  
  prompt()
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This defines the prompt that you want to send to the model.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Explain Java interfaces"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Write a SQL query to find duplicate users"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;where &lt;code&gt;message&lt;/code&gt; comes from an HTTP request.&lt;/p&gt;




&lt;h2&gt;
  
  
  call()
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This executes the model interaction.&lt;/p&gt;

&lt;p&gt;You can think about it as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Build Prompt
     |
     v
Call Model
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  content()
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This extracts the generated text from the response.&lt;/p&gt;

&lt;p&gt;So the entire chain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;chatClient&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;can be mentally understood as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create Prompt
     |
     v
Call AI Model
     |
     v
Extract Text
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This fluent style is one of the reasons &lt;code&gt;ChatClient&lt;/code&gt; is convenient for application developers.&lt;/p&gt;




&lt;h1&gt;
  
  
  Step 7: Add a Service Layer
&lt;/h1&gt;

&lt;p&gt;Although putting the AI call directly inside a controller works for a small demonstration, it is not how I would structure a production application.&lt;/p&gt;

&lt;p&gt;Instead, let's introduce a service.&lt;/p&gt;

&lt;p&gt;Our architecture becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client
  |
  v
Controller
  |
  v
Service
  |
  v
ChatClient
  |
  v
OpenAI
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Service&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ChatService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ChatClient&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;ChatService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;chatClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;generateResponse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then our controller becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@RestController&lt;/span&gt;
&lt;span class="nd"&gt;@RequestMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ChatController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ChatService&lt;/span&gt; &lt;span class="n"&gt;chatService&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;ChatController&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChatService&lt;/span&gt; &lt;span class="n"&gt;chatService&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;chatService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chatService&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/chat"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@RequestParam&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;chatService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;generateResponse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This separation is much cleaner.&lt;/p&gt;

&lt;p&gt;The controller handles HTTP.&lt;/p&gt;

&lt;p&gt;The service handles AI interaction.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Separate the AI Logic?
&lt;/h1&gt;

&lt;p&gt;Imagine that six months from now your application has:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ChatController
EmailController
SupportController
DocumentController
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If every controller directly interacts with the AI model, your code can quickly become difficult to maintain.&lt;/p&gt;

&lt;p&gt;Instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Controllers
     |
     v
AI Services
     |
     v
ChatClient
     |
     v
ChatModel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps your application organized.&lt;/p&gt;

&lt;p&gt;It also makes it easier to add features later.&lt;/p&gt;




&lt;h1&gt;
  
  
  Step 8: Add System Instructions
&lt;/h1&gt;

&lt;p&gt;So far, we have only sent a user prompt.&lt;/p&gt;

&lt;p&gt;But real AI applications usually need more control.&lt;/p&gt;

&lt;p&gt;For example, imagine we are building a customer support assistant.&lt;/p&gt;

&lt;p&gt;We don't want the model to behave like a generic chatbot.&lt;/p&gt;

&lt;p&gt;We want to tell it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are a customer support assistant.
Answer clearly.
Keep responses concise.
Do not invent information.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can do that with a system message.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;system&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"""
                You are a helpful customer support assistant.
                Answer clearly and concisely.
                Do not invent information.
                """&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we have two different types of instructions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;System Message
      +
User Message
      |
      v
    Model
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This distinction will become extremely important later in the series.&lt;/p&gt;




&lt;h1&gt;
  
  
  System Message vs User Message
&lt;/h1&gt;

&lt;p&gt;Think about the two messages like this.&lt;/p&gt;

&lt;h3&gt;
  
  
  System message
&lt;/h3&gt;

&lt;p&gt;Defines the behavior of the assistant.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are a Java programming assistant.
Always provide production-quality examples.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  User message
&lt;/h3&gt;

&lt;p&gt;Contains the actual request.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Explain dependency injection.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;System
  |
  | "You are a Java assistant"
  |
  v
User
  |
  | "Explain dependency injection"
  |
  v
AI Model
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The PDF structure for this series introduces system and user messages as dedicated upcoming topics, so we will explore them in much more detail later.&lt;/p&gt;




&lt;h1&gt;
  
  
  Step 9: Build a Better AI Endpoint
&lt;/h1&gt;

&lt;p&gt;Let's make our API slightly more realistic.&lt;/p&gt;

&lt;p&gt;Instead of simply passing the user's message directly to the model, we can create a dedicated service method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Service&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ChatService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ChatClient&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;ChatService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;chatClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;ask&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;system&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"""
                        You are a helpful Java and Spring Boot assistant.
                        Explain technical concepts clearly.
                        Use examples when appropriate.
                        """&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@RestController&lt;/span&gt;
&lt;span class="nd"&gt;@RequestMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/chat"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ChatController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ChatService&lt;/span&gt; &lt;span class="n"&gt;chatService&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;ChatController&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChatService&lt;/span&gt; &lt;span class="n"&gt;chatService&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;chatService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chatService&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@GetMapping&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@RequestParam&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;chatService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ask&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now our API becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /api/chat?message=What is Spring Boot?
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and the service controls how the AI behaves.&lt;/p&gt;




&lt;h1&gt;
  
  
  What Happens Behind the Scenes?
&lt;/h1&gt;

&lt;p&gt;This simple line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;chatClient&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;hides several operations.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    Spring Boot
                         |
                         v
                    ChatClient
                         |
                         v
                 Build Chat Request
                         |
                         v
                    ChatModel
                         |
                         v
                  OpenAI Integration
                         |
                         v
                    OpenAI API
                         |
                         v
                    AI Response
                         |
                         v
                    ChatClient
                         |
                         v
                       String
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This abstraction is one of the major benefits of Spring AI.&lt;/p&gt;

&lt;p&gt;You focus on your application.&lt;/p&gt;

&lt;p&gt;Spring AI handles the model integration.&lt;/p&gt;




&lt;h1&gt;
  
  
  What If OpenAI Changes Its API?
&lt;/h1&gt;

&lt;p&gt;This is another reason abstractions are useful.&lt;/p&gt;

&lt;p&gt;Without Spring AI, you might build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spring Boot
     |
     v
Custom HTTP Client
     |
     v
OpenAI API
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your application would now contain provider-specific request and response handling.&lt;/p&gt;

&lt;p&gt;With Spring AI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spring Boot
     |
     v
ChatClient
     |
     v
ChatModel
     |
     v
Provider
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your application code stays focused on the AI interaction rather than provider-specific implementation details.&lt;/p&gt;

&lt;p&gt;This is the abstraction we discussed in the previous article.&lt;/p&gt;




&lt;h1&gt;
  
  
  A Better Mental Model
&lt;/h1&gt;

&lt;p&gt;If you're a Spring Boot developer, think about Spring AI in layers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application Layer
       |
       v
   ChatClient
       |
       v
    ChatModel
       |
       v
   AI Provider
       |
       v
    AI Model
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each layer has a responsibility.&lt;/p&gt;

&lt;h3&gt;
  
  
  Your application
&lt;/h3&gt;

&lt;p&gt;Business logic.&lt;/p&gt;

&lt;h3&gt;
  
  
  ChatClient
&lt;/h3&gt;

&lt;p&gt;Developer-friendly AI interaction.&lt;/p&gt;

&lt;h3&gt;
  
  
  ChatModel
&lt;/h3&gt;

&lt;p&gt;Model/provider abstraction.&lt;/p&gt;

&lt;h3&gt;
  
  
  AI Provider
&lt;/h3&gt;

&lt;p&gt;OpenAI, Ollama, AWS Bedrock, and other supported providers.&lt;/p&gt;

&lt;h3&gt;
  
  
  AI Model
&lt;/h3&gt;

&lt;p&gt;The actual language model generating the response.&lt;/p&gt;




&lt;h1&gt;
  
  
  Handling API Keys Correctly
&lt;/h1&gt;

&lt;p&gt;One of the biggest mistakes beginners make is committing API keys to Git.&lt;/p&gt;

&lt;p&gt;Never do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;spring.ai.openai.api-key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;sk-your-secret-key&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;inside a repository that will be shared publicly.&lt;/p&gt;

&lt;p&gt;Instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;spring.ai.openai.api-key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;${OPENAI_API_KEY}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and configure the environment variable separately.&lt;/p&gt;

&lt;p&gt;For local development:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OPENAI_API_KEY
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For production, use your deployment platform's secret management mechanism.&lt;/p&gt;

&lt;p&gt;The principle is simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Source Code
     X
     |
     | No secrets
     |
Environment / Secret Store
     |
     v
Spring Boot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Common Mistakes
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Mistake 1: Putting the API Key in Git
&lt;/h2&gt;

&lt;p&gt;Never commit secrets.&lt;/p&gt;

&lt;p&gt;If your key is exposed, rotate it immediately.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mistake 2: Calling the AI Model Directly From Every Controller
&lt;/h2&gt;

&lt;p&gt;Avoid this structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Controller 1 -&amp;gt; AI
Controller 2 -&amp;gt; AI
Controller 3 -&amp;gt; AI
Controller 4 -&amp;gt; AI
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Prefer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Controllers
     |
     v
Services
     |
     v
ChatClient
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Mistake 3: Treating ChatClient as the Model
&lt;/h2&gt;

&lt;p&gt;Remember:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ChatClient != AI Model
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ChatClient&lt;/code&gt; is the application-facing abstraction.&lt;/p&gt;

&lt;p&gt;The underlying model integration is handled through &lt;code&gt;ChatModel&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mistake 4: Sending Every Prompt Without Instructions
&lt;/h2&gt;

&lt;p&gt;A production AI application usually needs some control over model behavior.&lt;/p&gt;

&lt;p&gt;Instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you will often evolve toward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;system&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Later, we will see how prompt templates, advisors, memory, RAG, and tools make this even more powerful.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mistake 5: Starting With Complex AI Architecture
&lt;/h2&gt;

&lt;p&gt;You don't need this on day one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RAG
 +
Vector Database
 +
Tools
 +
MCP
 +
Memory
 +
Agents
 +
Multiple Models
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spring Boot
     |
     v
ChatClient
     |
     v
OpenAI
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add complexity when your application actually needs it.&lt;/p&gt;




&lt;h1&gt;
  
  
  From Simple Chat to Real AI Backend
&lt;/h1&gt;

&lt;p&gt;Our application currently looks simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client
   |
   v
REST API
   |
   v
ChatClient
   |
   v
OpenAI
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But this architecture can grow.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    Spring Boot
                         |
                         v
                    ChatClient
                         |
          +--------------+--------------+
          |              |              |
          v              v              v
        Memory          RAG           Tools
          |              |              |
          v              v              v
       History      Vector Store    Backend APIs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where Spring AI becomes much more interesting.&lt;/p&gt;

&lt;p&gt;A simple chatbot can eventually become a complete AI backend.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Complete Example
&lt;/h1&gt;

&lt;p&gt;Here is a simple production-style starting point.&lt;/p&gt;

&lt;h3&gt;
  
  
  ChatService
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Service&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ChatService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ChatClient&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;ChatService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;chatClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;ask&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;system&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"""
                        You are a helpful Java and Spring Boot assistant.
                        Explain concepts clearly and provide examples when useful.
                        """&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ChatController
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@RestController&lt;/span&gt;
&lt;span class="nd"&gt;@RequestMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/chat"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ChatController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ChatService&lt;/span&gt; &lt;span class="n"&gt;chatService&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;ChatController&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChatService&lt;/span&gt; &lt;span class="n"&gt;chatService&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;chatService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chatService&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@GetMapping&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@RequestParam&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;chatService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ask&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configuration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;spring.ai.openai.api-key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;${OPENAI_API_KEY}&lt;/span&gt;

&lt;span class="py"&gt;spring.ai.openai.chat.options.model&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;your-model&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result is a clean architecture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP Client
    |
    v
ChatController
    |
    v
ChatService
    |
    v
ChatClient
    |
    v
ChatModel
    |
    v
OpenAI
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  What You Learned
&lt;/h1&gt;

&lt;p&gt;In this article, we built our first Spring AI application using OpenAI.&lt;/p&gt;

&lt;p&gt;We learned how to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a Spring Boot project&lt;/li&gt;
&lt;li&gt;Add Spring AI's OpenAI integration&lt;/li&gt;
&lt;li&gt;Configure an OpenAI API key&lt;/li&gt;
&lt;li&gt;Configure a chat model&lt;/li&gt;
&lt;li&gt;Inject &lt;code&gt;ChatClient.Builder&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Build a &lt;code&gt;ChatClient&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Send prompts to an AI model&lt;/li&gt;
&lt;li&gt;Create an AI-powered REST endpoint&lt;/li&gt;
&lt;li&gt;Separate controller and AI service logic&lt;/li&gt;
&lt;li&gt;Use system and user messages&lt;/li&gt;
&lt;li&gt;Keep API keys outside source code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most importantly, you now understand the complete request flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client
   |
   v
Spring Boot
   |
   v
Controller
   |
   v
Service
   |
   v
ChatClient
   |
   v
ChatModel
   |
   v
OpenAI
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the foundation for everything we will build later.&lt;/p&gt;




&lt;h1&gt;
  
  
  What's Next?
&lt;/h1&gt;

&lt;p&gt;A hosted AI model is useful, but what if you want to run an LLM locally?&lt;/p&gt;

&lt;p&gt;Maybe you don't want to send your data to an external provider.&lt;/p&gt;

&lt;p&gt;Maybe you want to experiment without paying for API usage.&lt;/p&gt;

&lt;p&gt;Maybe you're building an application where local inference is important.&lt;/p&gt;

&lt;p&gt;That's where &lt;strong&gt;Ollama&lt;/strong&gt; comes in.&lt;/p&gt;

&lt;p&gt;In the next article, we will explore:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 4: Run Local LLMs with Ollama and Spring AI&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We will install Ollama, run a local model, connect it to Spring AI, and see how the same &lt;code&gt;ChatClient&lt;/code&gt; application can work with a local LLM.&lt;/p&gt;

&lt;p&gt;That is one of the most interesting parts of Spring AI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Same Application
       |
       +------&amp;gt; OpenAI
       |
       +------&amp;gt; Ollama
       |
       +------&amp;gt; Other Providers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application stays focused on the AI interaction while Spring AI handles the underlying model integration.&lt;/p&gt;




&lt;h1&gt;
  
  
  Frequently Asked Questions
&lt;/h1&gt;

&lt;h2&gt;
  
  
  What is Spring AI?
&lt;/h2&gt;

&lt;p&gt;Spring AI is an abstraction layer that makes it easier for Spring applications to integrate with AI models and AI-related capabilities.&lt;/p&gt;




&lt;h2&gt;
  
  
  Can I use OpenAI with Spring Boot?
&lt;/h2&gt;

&lt;p&gt;Yes. Spring AI provides an OpenAI integration that allows Spring Boot applications to communicate with OpenAI models.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where should I store my OpenAI API key?
&lt;/h2&gt;

&lt;p&gt;Do not hard-code it in your source code. Use environment variables or a proper secret-management solution.&lt;/p&gt;




&lt;h2&gt;
  
  
  Do I need ChatModel directly?
&lt;/h2&gt;

&lt;p&gt;For most application-level use cases, you can work primarily with &lt;code&gt;ChatClient&lt;/code&gt;. &lt;code&gt;ChatModel&lt;/code&gt; remains important because it represents the underlying model integration.&lt;/p&gt;




&lt;h2&gt;
  
  
  Can I change the AI provider later?
&lt;/h2&gt;

&lt;p&gt;One of the goals of Spring AI's abstraction model is to reduce application coupling to provider-specific implementation details.&lt;/p&gt;




&lt;h2&gt;
  
  
  What should I learn after this?
&lt;/h2&gt;

&lt;p&gt;The next step in this series is running a local LLM with Ollama and Spring AI.&lt;/p&gt;




&lt;h1&gt;
  
  
  Spring AI Series
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Part 1:&lt;/strong&gt; Spring AI Tutorial: How Java Developers Can Build Generative AI Applications with Spring Boot&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 2:&lt;/strong&gt; ChatModel vs ChatClient in Spring AI&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 3:&lt;/strong&gt; Build Your First Spring AI Application with OpenAI&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 4:&lt;/strong&gt; Run Local LLMs with Ollama and Spring AI&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 5:&lt;/strong&gt; Run AI Models Locally with Docker Model Runner&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 6:&lt;/strong&gt; Using AWS Bedrock with Spring AI&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 7:&lt;/strong&gt; Working with Multiple Chat Models in Spring AI&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 8:&lt;/strong&gt; Understanding Message Roles in LLMs&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 9:&lt;/strong&gt; System Messages and User Messages in Spring AI&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 10:&lt;/strong&gt; Configuring Default Behavior in ChatClient&lt;/p&gt;

&lt;p&gt;The series will continue into prompt templates, advisors, structured output, tokens, embeddings, chat memory, RAG, vector stores, tool calling, MCP, evaluation, observability, and AI agents.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>springai</category>
      <category>springboot</category>
      <category>java</category>
    </item>
    <item>
      <title>ChatModel vs ChatClient in Spring AI: Understanding the Core Abstractions</title>
      <dc:creator>Ayush Shrivastava</dc:creator>
      <pubDate>Wed, 12 Aug 2026 11:53:42 +0000</pubDate>
      <link>https://dev.to/ayshriv/chatmodel-vs-chatclient-in-spring-ai-understanding-the-core-abstractions-4bg9</link>
      <guid>https://dev.to/ayshriv/chatmodel-vs-chatclient-in-spring-ai-understanding-the-core-abstractions-4bg9</guid>
      <description>&lt;p&gt;In the first article of this Spring AI series, we learned what Spring AI is and built a simple Generative AI application using Spring Boot.&lt;/p&gt;

&lt;p&gt;Now it is time to understand two of the most important concepts in Spring AI:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ChatModel&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ChatClient&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are coming from Java and Spring Boot, understanding the difference between these two will make the rest of Spring AI much easier to learn.&lt;/p&gt;

&lt;p&gt;The simple relationship is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your Java Code
      |
      v
  ChatClient
      |
      v
   ChatModel
      |
      v
 AI Provider
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;ChatClient&lt;/code&gt; provides the developer-friendly API, while the &lt;code&gt;ChatModel&lt;/code&gt; represents the underlying AI model integration.&lt;/p&gt;

&lt;p&gt;Let's understand this with a real example.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is ChatModel in Spring AI?
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ChatModel&lt;/code&gt; is a lower-level abstraction for communicating with an AI model.&lt;/p&gt;

&lt;p&gt;It provides the contract that Spring AI uses to interact with different AI providers.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OpenAI
Azure OpenAI
Google
Mistral
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Spring AI can provide provider-specific implementations such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OpenAiChatModel
GeminiChatModel
MistralChatModel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The purpose of &lt;code&gt;ChatModel&lt;/code&gt; is to hide provider-specific communication details behind a common abstraction.&lt;/p&gt;

&lt;p&gt;Think about it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ChatModel
    |
    +-- OpenAI
    |
    +-- Gemini
    |
    +-- Mistral
    |
    +-- Other providers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your application can work with the abstraction instead of directly building HTTP requests for every provider.&lt;/p&gt;




&lt;h1&gt;
  
  
  What Is ChatClient?
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;ChatClient&lt;/code&gt; is a higher-level abstraction built on top of &lt;code&gt;ChatModel&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It provides a fluent API that makes it easier to work with AI models.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Explain Spring Boot dependency injection"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of manually handling the lower-level model interaction, you work with a much simpler API.&lt;/p&gt;

&lt;p&gt;The PDF describes &lt;code&gt;ChatClient&lt;/code&gt; as handling prompt construction, chat history, model invocation, and extracting response content.&lt;/p&gt;




&lt;h1&gt;
  
  
  ChatModel vs ChatClient
&lt;/h1&gt;

&lt;p&gt;Here is the easiest way to remember the difference:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ChatModel&lt;/th&gt;
&lt;th&gt;ChatClient&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Lower-level abstraction&lt;/td&gt;
&lt;td&gt;Higher-level abstraction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Represents AI model integration&lt;/td&gt;
&lt;td&gt;Developer-friendly interface&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Handles provider communication&lt;/td&gt;
&lt;td&gt;Builds and manages prompts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;More technical&lt;/td&gt;
&lt;td&gt;Fluent and easier to use&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Used underneath ChatClient&lt;/td&gt;
&lt;td&gt;Uses ChatModel internally&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A simple analogy from the course material is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ChatModel  = Engine

ChatClient = Steering wheel + Dashboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The engine performs the actual work.&lt;/p&gt;

&lt;p&gt;The steering wheel and dashboard give you an easier way to control it.&lt;/p&gt;




&lt;h1&gt;
  
  
  How ChatClient and ChatModel Work Together
&lt;/h1&gt;

&lt;p&gt;Suppose you write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;chatClient&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"What is Spring Boot?"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What actually happens?&lt;/p&gt;

&lt;p&gt;The flow is approximately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your Java Code
      |
      v
ChatClient
      |
      v
Prompt
      |
      v
ChatModel
      |
      v
AI Provider API
      |
      v
AI Response
      |
      v
ChatClient
      |
      v
String Response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Spring Boot auto-configuration creates the appropriate &lt;code&gt;ChatModel&lt;/code&gt; based on your configured provider.&lt;/p&gt;

&lt;p&gt;Spring AI then provides an auto-configured &lt;code&gt;ChatClient.Builder&lt;/code&gt; that is wired to the model.&lt;/p&gt;

&lt;p&gt;When you call the fluent &lt;code&gt;ChatClient&lt;/code&gt; API, the request eventually delegates to the underlying &lt;code&gt;ChatModel&lt;/code&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  Let's Build a Real Example
&lt;/h1&gt;

&lt;p&gt;Let's create a simple Spring Boot API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /api/chat?message=Explain dependency injection
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The API should send the message to an AI model and return the generated response.&lt;/p&gt;

&lt;p&gt;Our architecture will be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client
  |
  | GET /api/chat
  v
Spring Boot Controller
  |
  v
ChatClient
  |
  v
ChatModel
  |
  v
AI Provider
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Step 1: Create the ChatController
&lt;/h1&gt;

&lt;p&gt;Create:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@RestController&lt;/span&gt;
&lt;span class="nd"&gt;@RequestMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ChatController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ChatClient&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;ChatController&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt; &lt;span class="n"&gt;chatClientBuilder&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;chatClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chatClientBuilder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/chat"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@RequestParam&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"message"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;ChatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Spring Boot provides this builder through auto-configuration when the appropriate Spring AI model dependency is present.&lt;/p&gt;

&lt;p&gt;Then we create our client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;chatClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chatClientBuilder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can use it anywhere inside our service or controller.&lt;/p&gt;




&lt;h1&gt;
  
  
  Step 2: Send a Prompt
&lt;/h1&gt;

&lt;p&gt;Let's send:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /api/chat?message=What is dependency injection in Spring?
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;chatClient&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;can be understood as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;prompt()
    |
    | Build the request
    v
call()
    |
    | Execute the model call
    v
content()
    |
    | Extract generated text
    v
String
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is one of the biggest advantages of &lt;code&gt;ChatClient&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The API is simple enough that a Java developer can understand what is happening without dealing directly with provider-specific request objects.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Not Use ChatModel Directly?
&lt;/h1&gt;

&lt;p&gt;You might now ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If ChatModel communicates with the AI provider, why do we need ChatClient?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can work at the &lt;code&gt;ChatModel&lt;/code&gt; level when you need lower-level control.&lt;/p&gt;

&lt;p&gt;But most application developers usually want to express something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;chatClient&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Explain Java records"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;rather than manually constructing model requests.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;ChatClient&lt;/code&gt; abstraction provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fluent APIs&lt;/li&gt;
&lt;li&gt;Prompt construction&lt;/li&gt;
&lt;li&gt;Message handling&lt;/li&gt;
&lt;li&gt;Model invocation&lt;/li&gt;
&lt;li&gt;Response extraction&lt;/li&gt;
&lt;li&gt;Synchronous and streaming programming models&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are some of the capabilities highlighted in the Spring AI course material.&lt;/p&gt;




&lt;h1&gt;
  
  
  A Spring Boot Developer's Mental Model
&lt;/h1&gt;

&lt;p&gt;If you already know Spring Data, you can think about abstractions.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spring Data
    |
    v
Repository Abstraction
    |
    v
Database Implementation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Similarly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spring AI
    |
    v
ChatClient
    |
    v
ChatModel
    |
    v
AI Provider
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The goal is to avoid coupling your application code unnecessarily to the implementation details.&lt;/p&gt;




&lt;h1&gt;
  
  
  What Happens During Spring Boot Auto-Configuration?
&lt;/h1&gt;

&lt;p&gt;One of the useful features of Spring AI is Spring Boot auto-configuration.&lt;/p&gt;

&lt;p&gt;When you add the appropriate Spring AI dependency and configuration, Spring Boot can create the necessary model bean.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spring Boot Application Starts
            |
            v
Spring AI Configuration
            |
            v
Create ChatModel Bean
            |
            v
Create ChatClient.Builder
            |
            v
Your Controller
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You don't have to manually create every object.&lt;/p&gt;

&lt;p&gt;You simply inject:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;ChatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and build the client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;chatClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chatClientBuilder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This follows the familiar Spring dependency injection model.&lt;/p&gt;




&lt;h1&gt;
  
  
  ChatClient Is More Than a Simple Wrapper
&lt;/h1&gt;

&lt;p&gt;It might initially look like &lt;code&gt;ChatClient&lt;/code&gt; is just a shortcut for calling the model.&lt;/p&gt;

&lt;p&gt;But it becomes much more useful as your application grows.&lt;/p&gt;

&lt;p&gt;For example, Spring AI allows you to configure things such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;System instructions
Advisors
Tools
Options
Prompt templates
Chat memory
Streaming
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The PDF later introduces these capabilities as part of the Spring AI journey.&lt;/p&gt;

&lt;p&gt;This means your code can evolve from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;chatClient&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to something more sophisticated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;chatClient&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;system&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"You are a helpful support assistant."&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And eventually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ChatClient
   |
   +-- System Instructions
   |
   +-- Memory
   |
   +-- Advisors
   |
   +-- Tools
   |
   +-- RAG
   |
   +-- Model
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is why learning &lt;code&gt;ChatClient&lt;/code&gt; early is important.&lt;/p&gt;




&lt;h1&gt;
  
  
  ChatClient Builder vs ChatClient
&lt;/h1&gt;

&lt;p&gt;There are two concepts you will frequently see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;ChatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;ChatClient&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They are not the same thing.&lt;/p&gt;

&lt;p&gt;The builder is used to configure and create a &lt;code&gt;ChatClient&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;ChatController&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt; &lt;span class="n"&gt;chatClientBuilder&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;chatClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chatClientBuilder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After calling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;ChatClient&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can then use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Think about it as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ChatClient.Builder
        |
        | build()
        v
   ChatClient
        |
        | prompt()
        v
   ChatModel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Using ChatClient With a Local Model
&lt;/h1&gt;

&lt;p&gt;One of the advantages of Spring AI is that you are not restricted to a hosted AI provider.&lt;/p&gt;

&lt;p&gt;The PDF introduces Ollama as a way to run LLMs locally and demonstrates configuring Spring AI to use it.&lt;/p&gt;

&lt;p&gt;For example, you can run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama run llama3.2:1b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then configure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;spring.ai.model.chat&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;ollama&lt;/span&gt;
&lt;span class="py"&gt;spring.ai.ollama.chat.model&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;llama3.2:1b&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the Ollama starter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework.ai&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-ai-starter-model-ollama&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your controller can remain almost identical:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@RestController&lt;/span&gt;
&lt;span class="nd"&gt;@RequestMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ChatController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ChatClient&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;ChatController&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;chatClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/chat"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@RequestParam&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a powerful concept.&lt;/p&gt;

&lt;p&gt;Your application code can remain focused on the AI interaction rather than provider-specific implementation details.&lt;/p&gt;




&lt;h1&gt;
  
  
  What If We Need Multiple Models?
&lt;/h1&gt;

&lt;p&gt;This is where things become more interesting.&lt;/p&gt;

&lt;p&gt;Imagine you have a production application with different requirements.&lt;/p&gt;

&lt;p&gt;You might want:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Simple Questions
        |
        v
Fast / Cheap Model

Complex Reasoning
        |
        v
Powerful Model

Local Development
        |
        v
Ollama
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why would you want multiple models?&lt;/p&gt;

&lt;p&gt;The PDF identifies several real-world reasons:&lt;/p&gt;

&lt;h3&gt;
  
  
  Task-Based Model Selection
&lt;/h3&gt;

&lt;p&gt;Use different models for different workloads.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Simple FAQ
   -&amp;gt; Lightweight model

Complex analysis
   -&amp;gt; More powerful model
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Fallback Strategy
&lt;/h3&gt;

&lt;p&gt;If one model is unavailable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Primary Model
      |
      X
      |
      v
Fallback Model
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  A/B Testing
&lt;/h3&gt;

&lt;p&gt;You can compare models based on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Accuracy
Latency
Cost
User experience
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  User Preferences
&lt;/h3&gt;

&lt;p&gt;Some applications may allow users to select a model.&lt;/p&gt;

&lt;h3&gt;
  
  
  Specialized Models
&lt;/h3&gt;

&lt;p&gt;You could use one model for coding and another for creative content.&lt;/p&gt;

&lt;p&gt;These scenarios are explicitly discussed in the PDF.&lt;/p&gt;




&lt;h1&gt;
  
  
  Multiple ChatClients
&lt;/h1&gt;

&lt;p&gt;Spring AI's default configuration provides a single &lt;code&gt;ChatClient.Builder&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That is enough for simple applications.&lt;/p&gt;

&lt;p&gt;But if your application needs multiple models, you may need to configure multiple clients yourself.&lt;/p&gt;

&lt;p&gt;The course material demonstrates disabling the default ChatClient builder auto-configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;spring.ai.chat.client.enabled&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and creating multiple &lt;code&gt;ChatClient&lt;/code&gt; instances manually.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Configuration&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ChatClientConfig&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Bean&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;ChatClient&lt;/span&gt; &lt;span class="nf"&gt;openAiChatClient&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OpenAiChatModel&lt;/span&gt; &lt;span class="n"&gt;chatModel&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;ChatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chatModel&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Bean&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;ChatClient&lt;/span&gt; &lt;span class="nf"&gt;ollamaChatClient&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OllamaChatModel&lt;/span&gt; &lt;span class="n"&gt;chatModel&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;ChatClient&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chatModel&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OpenAI ChatClient
       |
       v
OpenAI Model


Ollama ChatClient
       |
       v
Ollama Model
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives your application more flexibility.&lt;/p&gt;




&lt;h1&gt;
  
  
  A Real-World Architecture
&lt;/h1&gt;

&lt;p&gt;Imagine you're building an AI-powered customer support platform.&lt;/p&gt;

&lt;p&gt;You could have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 Customer
                    |
                    v
              Spring Boot API
                    |
                    v
                ChatClient
                    |
          +---------+---------+
          |                   |
          v                   v
     Fast Model         Powerful Model
          |                   |
          v                   v
     Simple FAQs       Complex Requests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Later, we might add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 ChatClient
                     |
       +-------------+-------------+
       |             |             |
       v             v             v
    Memory          RAG          Tools
       |             |             |
       v             v             v
   History       Company Docs   Backend APIs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the direction in which a simple Spring AI application grows into a real AI backend.&lt;/p&gt;




&lt;h1&gt;
  
  
  ChatModel or ChatClient: Which One Should You Use?
&lt;/h1&gt;

&lt;p&gt;For most application-level Spring AI development:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Prefer ChatClient
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;because it provides the higher-level API and integrates naturally with prompts, advisors, memory, tools, and other application concerns.&lt;/p&gt;

&lt;p&gt;You should understand &lt;code&gt;ChatModel&lt;/code&gt; because it is the underlying abstraction that connects your application to the actual model provider.&lt;/p&gt;

&lt;p&gt;The mental model is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ChatClient
    |
    | High-level developer API
    v
ChatModel
    |
    | Provider integration
    v
AI Model
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Common Mistakes
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Mistake 1: Thinking ChatClient Is the AI Model
&lt;/h2&gt;

&lt;p&gt;It is not.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ChatClient != LLM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ChatClient is the interface your application uses to interact with the model.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mistake 2: Thinking ChatModel and ChatClient Are Competing Alternatives
&lt;/h2&gt;

&lt;p&gt;They serve different abstraction levels.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ChatClient
    |
    v
ChatModel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ChatClient uses ChatModel internally.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mistake 3: Hard-Coding Provider Logic Everywhere
&lt;/h2&gt;

&lt;p&gt;Avoid building your application around provider-specific HTTP calls when Spring AI already provides abstractions.&lt;/p&gt;

&lt;p&gt;Keep your application logic separated from the AI provider whenever possible.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mistake 4: Creating Multiple Clients Without a Reason
&lt;/h2&gt;

&lt;p&gt;Multiple models can be useful, but they also introduce additional configuration and operational complexity.&lt;/p&gt;

&lt;p&gt;Use them when you actually need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Fallbacks
Different workloads
A/B testing
Cost optimization
Specialized models
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Final Takeaway
&lt;/h1&gt;

&lt;p&gt;The most important thing to remember from this article is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ChatClient
    |
    | Developer-friendly API
    v
ChatModel
    |
    | Model/provider integration
    v
AI Provider
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ChatModel&lt;/code&gt; is the lower-level abstraction responsible for communicating with AI providers.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ChatClient&lt;/code&gt; provides the higher-level fluent API that makes AI interactions easier to build and maintain.&lt;/p&gt;

&lt;p&gt;For a typical Spring Boot application, you will spend much of your application-level development working with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;ChatClient&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;while Spring AI handles the underlying model integration.&lt;/p&gt;

&lt;p&gt;In the next article, we will take this knowledge and build a &lt;strong&gt;complete Spring AI application with OpenAI&lt;/strong&gt;, starting from project setup and configuration to creating a production-style REST endpoint.&lt;/p&gt;




&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is ChatModel in Spring AI?
&lt;/h3&gt;

&lt;p&gt;ChatModel is the lower-level abstraction used to communicate with an AI model provider.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is ChatClient?
&lt;/h3&gt;

&lt;p&gt;ChatClient is a higher-level Spring AI API that makes it easier to build prompts, invoke models, manage responses, and integrate additional AI application features.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is ChatClient better than ChatModel?
&lt;/h3&gt;

&lt;p&gt;They serve different purposes. ChatClient is generally more convenient for application development, while ChatModel provides the lower-level model abstraction.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can ChatClient work with Ollama?
&lt;/h3&gt;

&lt;p&gt;Yes. The course material demonstrates using ChatClient with an Ollama-backed model.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can Spring AI use multiple AI models?
&lt;/h3&gt;

&lt;p&gt;Yes. Spring AI can be configured with multiple ChatClient instances for different models and use cases.&lt;/p&gt;




&lt;h2&gt;
  
  
  Spring AI Series
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Part 1:&lt;/strong&gt; Spring AI Tutorial: How Java Developers Can Build Generative AI Applications with Spring Boot&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 2:&lt;/strong&gt; ChatModel vs ChatClient in Spring AI&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 3:&lt;/strong&gt; Build Your First Spring AI Application with OpenAI&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 4:&lt;/strong&gt; Run Local LLMs with Ollama and Spring AI&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 5:&lt;/strong&gt; Run AI Models Locally with Docker Model Runner&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 6:&lt;/strong&gt; Using AWS Bedrock with Spring AI&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 7:&lt;/strong&gt; Working with Multiple Chat Models in Spring AI&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 8:&lt;/strong&gt; Understanding Message Roles in LLMs&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 9:&lt;/strong&gt; System Messages and User Messages in Spring AI&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 10:&lt;/strong&gt; Configuring Default Behavior in ChatClient&lt;/p&gt;

&lt;p&gt;More articles will cover prompt templates, advisors, structured output, tokens, embeddings, chat memory, RAG, vector stores, tool calling, MCP, evaluation, observability, and AI agents.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>springboot</category>
      <category>java</category>
      <category>chatgpt</category>
    </item>
    <item>
      <title>Spring AI Tutorial: How Java Developers Can Build Generative AI Applications with Spring Boot</title>
      <dc:creator>Ayush Shrivastava</dc:creator>
      <pubDate>Tue, 11 Aug 2026 10:43:51 +0000</pubDate>
      <link>https://dev.to/ayshriv/spring-ai-tutorial-how-java-developers-can-build-generative-ai-applications-with-spring-boot-2m4e</link>
      <guid>https://dev.to/ayshriv/spring-ai-tutorial-how-java-developers-can-build-generative-ai-applications-with-spring-boot-2m4e</guid>
      <description>&lt;p&gt;If you are a Java developer working with Spring Boot, you already know how to build REST APIs, microservices, database-driven applications, authentication systems, and production backends.&lt;/p&gt;

&lt;p&gt;But AI development introduces a new set of questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How do I connect an LLM to my Spring Boot application?&lt;/li&gt;
&lt;li&gt;How do I send prompts from Java?&lt;/li&gt;
&lt;li&gt;How do I work with OpenAI or local models?&lt;/li&gt;
&lt;li&gt;How do I give an AI application access to my application's data?&lt;/li&gt;
&lt;li&gt;How can an LLM call my Java methods?&lt;/li&gt;
&lt;li&gt;How do I build RAG applications with Spring Boot?&lt;/li&gt;
&lt;li&gt;How do I build AI agents instead of simple chatbots?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where &lt;strong&gt;Spring AI&lt;/strong&gt; becomes interesting.&lt;/p&gt;

&lt;p&gt;Spring AI brings AI application development into the Spring ecosystem and provides abstractions for working with AI models, embeddings, vector stores, memory, tools, and other AI application patterns.&lt;/p&gt;

&lt;p&gt;According to the Spring AI roadmap in this series, we will move from a simple AI application to concepts such as RAG, tool calling, MCP, evaluation, observability, and multimodal applications.&lt;/p&gt;

&lt;p&gt;In this article, we will start from the beginning.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is Spring AI?
&lt;/h2&gt;

&lt;p&gt;Spring AI is a framework for integrating AI capabilities into Spring applications.&lt;/p&gt;

&lt;p&gt;The idea is simple:&lt;/p&gt;

&lt;p&gt;Instead of learning a completely different programming model for AI, Spring developers can use familiar Spring concepts and abstractions to communicate with AI models.&lt;/p&gt;

&lt;p&gt;Spring AI provides integrations with multiple AI providers and supports capabilities such as chat models, embeddings, vector stores, memory, tool calling, MCP, and AI observability.&lt;/p&gt;

&lt;p&gt;You can think about the architecture like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spring Boot Application
        |
        v
     Spring AI
        |
        +------------------+
        |                  |
        v                  v
   Chat Model          Embedding Model
        |                  |
        v                  v
     LLM API          Vector Store
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part is that your business application remains a Spring Boot application.&lt;/p&gt;

&lt;p&gt;You are simply adding an AI layer to it.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Should Java Developers Learn Spring AI?
&lt;/h1&gt;

&lt;p&gt;Imagine that you are building an employee management application.&lt;/p&gt;

&lt;p&gt;Your application already has:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Java
Spring Boot
Spring Security
PostgreSQL
REST APIs
Docker
AWS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now your product team asks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Can we add an AI assistant that answers employee questions?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User:
How many paid leaves can I take every year?

AI Assistant:
According to the company policy, employees are eligible
for 18 days of paid leave annually.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A traditional backend developer might think:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Frontend
   |
   v
Spring Boot
   |
   v
Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But an AI-powered application may look more like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Frontend
   |
   v
Spring Boot
   |
   v
Spring AI
   |
   +------&amp;gt; LLM
   |
   +------&amp;gt; Vector Database
   |
   +------&amp;gt; Internal APIs
   |
   +------&amp;gt; Business Logic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the shift from building traditional backend systems to building &lt;strong&gt;AI-powered backend systems&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Spring AI provides the abstractions needed to connect these pieces.&lt;/p&gt;




&lt;h1&gt;
  
  
  Spring AI Is Not an AI Model
&lt;/h1&gt;

&lt;p&gt;This distinction is important.&lt;/p&gt;

&lt;p&gt;Spring AI is not itself an LLM.&lt;/p&gt;

&lt;p&gt;It is an application framework that helps your Spring application communicate with AI models.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your Spring Boot Application
            |
            v
        Spring AI
            |
       +----+----+
       |         |
       v         v
    OpenAI     Ollama
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model is responsible for generating the response.&lt;/p&gt;

&lt;p&gt;Spring AI provides the developer-friendly interface for communicating with that model.&lt;/p&gt;

&lt;p&gt;This separation is useful because your business logic does not have to be tightly coupled to a single AI provider.&lt;/p&gt;




&lt;h1&gt;
  
  
  What Can You Build With Spring AI?
&lt;/h1&gt;

&lt;p&gt;Spring AI can be used for much more than a basic chatbot.&lt;/p&gt;

&lt;p&gt;Some examples include:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. AI Customer Support
&lt;/h3&gt;

&lt;p&gt;A customer asks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Where is my order?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your application can combine AI with backend APIs to retrieve order information and generate a natural response.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Document Question Answering
&lt;/h3&gt;

&lt;p&gt;Suppose your company has:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;employee-policy.pdf
leave-policy.pdf
insurance-policy.pdf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of manually searching these documents, users can ask:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;How many annual leaves do I get?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A RAG pipeline can retrieve the relevant document content and provide it to the LLM. The PDF's roadmap specifically covers chunking, embeddings, vector stores, and document retrieval for this purpose.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. AI-Powered Recommendations
&lt;/h3&gt;

&lt;p&gt;You can use embeddings to understand user intent and build semantic recommendations.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. AI Data Processing
&lt;/h3&gt;

&lt;p&gt;Spring AI can also be used for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Text generation&lt;/li&gt;
&lt;li&gt;Content moderation&lt;/li&gt;
&lt;li&gt;Transcription&lt;/li&gt;
&lt;li&gt;Summarization&lt;/li&gt;
&lt;li&gt;Classification&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are among the use cases highlighted in the course material.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. AI Agents
&lt;/h3&gt;

&lt;p&gt;Eventually, your application can move beyond simply generating text.&lt;/p&gt;

&lt;p&gt;An AI system can:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Understand request
      |
      v
Retrieve information
      |
      v
Call Java method
      |
      v
Call external API
      |
      v
Make another decision
      |
      v
Return final response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where concepts such as tool calling and agentic AI become important.&lt;/p&gt;




&lt;h1&gt;
  
  
  The First Spring AI Application
&lt;/h1&gt;

&lt;p&gt;Let's build something simple.&lt;/p&gt;

&lt;p&gt;Our first application will accept a message through a REST API and send that message to an AI model.&lt;/p&gt;

&lt;p&gt;The flow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP Request
     |
     v
Spring Boot Controller
     |
     v
ChatClient
     |
     v
ChatModel
     |
     v
AI Provider
     |
     v
Response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is intentionally simple.&lt;/p&gt;

&lt;p&gt;The goal is to understand the basic Spring AI programming model before introducing RAG, memory, tools, or agents.&lt;/p&gt;




&lt;h1&gt;
  
  
  Step 1: Create a Spring Boot Project
&lt;/h1&gt;

&lt;p&gt;Create a Spring Boot project with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Java
Spring Boot
Spring Web
Spring AI
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The PDF uses the Spring Web MVC starter and the Spring AI OpenAI model starter for the initial example.&lt;/p&gt;

&lt;p&gt;Your Maven dependencies can look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependencies&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework.boot&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-boot-starter-webmvc&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework.ai&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-ai-starter-model-openai&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/dependencies&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact Spring AI dependency/version should be aligned with the Spring AI version you choose for your project.&lt;/p&gt;




&lt;h1&gt;
  
  
  Step 2: Configure the API Key
&lt;/h1&gt;

&lt;p&gt;If you are using a hosted model provider, you need credentials to communicate with it.&lt;/p&gt;

&lt;p&gt;Do not hard-code API keys inside your Java source code.&lt;/p&gt;

&lt;p&gt;Instead, use an environment variable.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;spring.ai.openai.api-key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;${OPENAI_API_KEY}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then configure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OPENAI_API_KEY=your-api-key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The course material also follows this approach by resolving the API key from an environment variable.&lt;/p&gt;

&lt;p&gt;This is much better than:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;apiKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"sk-xxxxxxxx"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Never commit secrets into Git.&lt;/p&gt;




&lt;h1&gt;
  
  
  Step 3: Create the Chat Controller
&lt;/h1&gt;

&lt;p&gt;Now let's create our first Spring AI endpoint.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@RestController&lt;/span&gt;
&lt;span class="nd"&gt;@RequestMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ChatController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ChatClient&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;ChatController&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt; &lt;span class="n"&gt;chatClientBuilder&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;chatClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chatClientBuilder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/chat"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@RequestParam&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"message"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the core of our first application.&lt;/p&gt;

&lt;p&gt;The PDF uses the same basic approach: inject &lt;code&gt;ChatClient.Builder&lt;/code&gt;, build a &lt;code&gt;ChatClient&lt;/code&gt;, provide a user message, call the model, and extract the response content.&lt;/p&gt;




&lt;h1&gt;
  
  
  Step 4: Run the Application
&lt;/h1&gt;

&lt;p&gt;Start your Spring Boot application.&lt;/p&gt;

&lt;p&gt;Now make a request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /api/chat?message=What is Spring Boot?
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The request reaches:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ChatController
      |
      v
ChatClient
      |
      v
AI Model
      |
      v
Generated Response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You might receive something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spring Boot is a framework built on top of Spring
that simplifies the development of Java applications...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You have now created your first Spring AI application.&lt;/p&gt;




&lt;h1&gt;
  
  
  Understanding ChatClient
&lt;/h1&gt;

&lt;p&gt;The most important class in this example is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;ChatClient&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ChatClient provides a higher-level API for interacting with an AI model.&lt;/p&gt;

&lt;p&gt;It gives us a fluent programming style:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;chatClient&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is much easier to work with than manually constructing provider-specific HTTP requests.&lt;/p&gt;

&lt;p&gt;The course material describes &lt;code&gt;ChatClient&lt;/code&gt; as the higher-level developer-friendly abstraction that handles prompt construction, message handling, model invocation, and extracting content from the response.&lt;/p&gt;




&lt;h1&gt;
  
  
  ChatModel vs ChatClient
&lt;/h1&gt;

&lt;p&gt;This is one of the first concepts you should understand when learning Spring AI.&lt;/p&gt;

&lt;p&gt;There are two important abstractions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ChatClient
    |
    v
ChatModel
    |
    v
AI Provider
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What Is ChatModel?
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ChatModel&lt;/code&gt; represents the lower-level interface for communicating with an AI model.&lt;/p&gt;

&lt;p&gt;It handles things such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Communication with AI providers&lt;/li&gt;
&lt;li&gt;Model-specific configuration&lt;/li&gt;
&lt;li&gt;API interaction&lt;/li&gt;
&lt;li&gt;AI request and response handling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The PDF describes implementations such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OpenAiChatModel
GeminiChatModel
MistralChatModel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;depending on the provider being used.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is ChatClient?
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ChatClient&lt;/code&gt; sits at a higher level.&lt;/p&gt;

&lt;p&gt;Instead of worrying about the underlying provider API, you can write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;chatClient&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Explain dependency injection"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The relationship can be visualized as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your Java Code
      |
      v
   ChatClient
      |
      v
   ChatModel
      |
      v
AI Provider API
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A useful mental model is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ChatModel  = Engine

ChatClient = Steering wheel + Dashboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The engine does the actual work.&lt;/p&gt;

&lt;p&gt;The dashboard gives you a convenient interface for controlling it.&lt;/p&gt;

&lt;p&gt;The course material describes this same relationship between &lt;code&gt;ChatClient&lt;/code&gt; and &lt;code&gt;ChatModel&lt;/code&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Is This Abstraction Useful?
&lt;/h1&gt;

&lt;p&gt;Imagine your application starts with one AI provider.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spring Boot
    |
    v
Spring AI
    |
    v
Provider A
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Later, you decide that some workloads should use another model.&lt;/p&gt;

&lt;p&gt;You don't want your entire application to become:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;OpenAIHttpClient&lt;/span&gt;
&lt;span class="nc"&gt;OpenAIRequest&lt;/span&gt;
&lt;span class="nc"&gt;OpenAIResponse&lt;/span&gt;
&lt;span class="nc"&gt;OpenAIAuthentication&lt;/span&gt;
&lt;span class="nc"&gt;OpenAIModelConfiguration&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;everywhere.&lt;/p&gt;

&lt;p&gt;Instead, Spring AI provides abstractions that allow you to work at the application level.&lt;/p&gt;

&lt;p&gt;This becomes particularly valuable when you have multiple models.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Simple Question
      |
      v
Lightweight Model

Complex Reasoning
      |
      v
Powerful Model

Local Development
      |
      v
Ollama
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The course material identifies task-based model selection, fallback strategies, A/B testing, user preferences, and specialized models as reasons to work with multiple chat models.&lt;/p&gt;




&lt;h1&gt;
  
  
  Spring AI Is Not Limited to OpenAI
&lt;/h1&gt;

&lt;p&gt;One of the important benefits of Spring AI is that it is designed around multiple providers.&lt;/p&gt;

&lt;p&gt;The course material covers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OpenAI
Ollama
Docker Model Runner
AWS Bedrock
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;as part of the introductory journey.&lt;/p&gt;

&lt;p&gt;For example, Ollama allows developers to run models locally.&lt;/p&gt;

&lt;p&gt;The course demonstrates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama run llama3.2:1b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and then configures Spring AI to use the local model.&lt;/p&gt;

&lt;p&gt;This can be useful during development when you want to experiment with local models instead of depending entirely on a hosted API.&lt;/p&gt;




&lt;h1&gt;
  
  
  What About AWS Bedrock?
&lt;/h1&gt;

&lt;p&gt;If you are already building applications on AWS, another option covered in the course is Amazon Bedrock.&lt;/p&gt;

&lt;p&gt;Bedrock is a managed AWS service for building generative AI applications and provides access to foundation models from multiple vendors.&lt;/p&gt;

&lt;p&gt;Your architecture can therefore look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spring Boot
     |
     v
Spring AI
     |
     v
AWS Bedrock
     |
     v
Foundation Model
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is particularly interesting for enterprise applications that already operate inside AWS environments.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Bigger Spring AI Journey
&lt;/h1&gt;

&lt;p&gt;Our first example is intentionally tiny.&lt;/p&gt;

&lt;p&gt;In a production application, simply sending a prompt to an LLM is rarely enough.&lt;/p&gt;

&lt;p&gt;Consider a customer support application.&lt;/p&gt;

&lt;p&gt;A basic version might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User
 |
 v
ChatClient
 |
 v
LLM
 |
 v
Answer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But a real production system may need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    +----------------+
                    |  Chat Memory   |
                    +-------+--------+
                            |
                            v
User --&amp;gt; ChatClient --&amp;gt; Advisor --&amp;gt; LLM
                            |
             +--------------+--------------+
             |                             |
             v                             v
        Vector Store                  Java Tools
             |                             |
             v                             v
        Company Docs                 Backend APIs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then we can introduce:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RAG
Embeddings
Vector Databases
Tool Calling
MCP
Evaluators
Observability
Memory
Streaming
Structured Output
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These concepts form the larger Spring AI journey outlined in the PDF.&lt;/p&gt;




&lt;h1&gt;
  
  
  From Chatbots to AI Applications
&lt;/h1&gt;

&lt;p&gt;There is an important progression to understand.&lt;/p&gt;

&lt;p&gt;A simple LLM application looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LLM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It can answer questions based primarily on what it learned during training.&lt;/p&gt;

&lt;p&gt;Then we add external knowledge:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LLM + RAG
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now it can retrieve information from our documents or knowledge base.&lt;/p&gt;

&lt;p&gt;Then we add tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LLM + RAG + Tools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now it can retrieve information and interact with external systems.&lt;/p&gt;

&lt;p&gt;Finally, we can move toward agentic systems:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Agentic AI
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;where the application can reason through tasks, use tools, retrieve information, and execute multiple steps.&lt;/p&gt;

&lt;p&gt;The PDF presents this progression from LLM to RAG, tools, and agentic AI as the broader generative AI journey.&lt;/p&gt;

&lt;p&gt;This is where Spring AI becomes particularly interesting for backend developers.&lt;/p&gt;




&lt;h1&gt;
  
  
  A Real-World Example
&lt;/h1&gt;

&lt;p&gt;Let's take an e-commerce application.&lt;/p&gt;

&lt;p&gt;A user asks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Where is my order and when will it arrive?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A normal LLM cannot automatically know the user's latest order.&lt;/p&gt;

&lt;p&gt;Your backend might have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PostgreSQL
    |
    +-- orders
    +-- customers
    +-- payments
    +-- shipments
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AI application needs access to that real-time information.&lt;/p&gt;

&lt;p&gt;A future Spring AI application could look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User
 |
 v
Spring Boot API
 |
 v
Spring AI
 |
 +----&amp;gt; LLM
 |
 +----&amp;gt; Order Tool
 |          |
 |          v
 |       Database
 |
 +----&amp;gt; Shipping Tool
            |
            v
        Shipping API
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the AI is not simply generating text.&lt;/p&gt;

&lt;p&gt;It is interacting with your backend.&lt;/p&gt;

&lt;p&gt;This is the foundation for tool calling, which the course introduces as a way to allow an AI application to access current data and perform actions through Java code.&lt;/p&gt;




&lt;h1&gt;
  
  
  What We Will Build Throughout This Series
&lt;/h1&gt;

&lt;p&gt;This article is only the starting point.&lt;/p&gt;

&lt;p&gt;The complete series will gradually move from basic Spring AI concepts to production-oriented AI application development.&lt;/p&gt;

&lt;p&gt;The learning path will look approximately like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spring AI Fundamentals
        |
        v
ChatClient and ChatModel
        |
        v
Prompts and Message Roles
        |
        v
Prompt Templates
        |
        v
Chat Options
        |
        v
Structured Output
        |
        v
Generative AI Fundamentals
        |
        v
Tokens and Embeddings
        |
        v
Chat Memory
        |
        v
RAG
        |
        v
Vector Databases
        |
        v
Tool Calling
        |
        v
MCP
        |
        v
AI Evaluation
        |
        v
Observability
        |
        v
AI Agents
        |
        v
Production AI Applications
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The source material specifically includes prompt templates, streaming, ChatOptions, advisors, prompt stuffing, structured output, memory, RAG, tool calling, MCP, evaluators, observability, voice, and image generation.
&lt;/h2&gt;

&lt;h1&gt;
  
  
  What You Should Know Before Starting
&lt;/h1&gt;

&lt;p&gt;This series is designed primarily for developers who already understand Java and Spring Boot.&lt;/p&gt;

&lt;p&gt;You should be comfortable with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Java
Spring Boot
REST APIs
Basic Docker
Basic Postman
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You do not need to be an AI researcher.&lt;/p&gt;

&lt;p&gt;You do not need to understand neural networks before writing your first Spring AI application.&lt;/p&gt;

&lt;p&gt;We will learn the AI concepts gradually and connect them back to backend development.&lt;/p&gt;

&lt;p&gt;The source material also lists Java, Spring Boot, Docker, and Postman familiarity as prerequisites.&lt;/p&gt;




&lt;h1&gt;
  
  
  Final Thoughts
&lt;/h1&gt;

&lt;p&gt;If you are already a Java and Spring Boot developer, learning AI application development does not mean throwing away everything you already know.&lt;/p&gt;

&lt;p&gt;In fact, your backend experience is extremely useful.&lt;/p&gt;

&lt;p&gt;You already understand:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;APIs
Databases
Authentication
Security
Microservices
Caching
Cloud
Distributed Systems
Testing
Observability
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you are adding another layer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI Models
Prompts
Embeddings
RAG
Tools
Memory
Agents
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Spring AI helps bring these AI capabilities into the Spring ecosystem using familiar application-development patterns.&lt;/p&gt;

&lt;p&gt;And that is the goal of this series:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Go from Java Developer to AI Engineer by building real AI-powered backend applications with Spring AI.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In the next article, we will go one level deeper into &lt;strong&gt;ChatModel vs ChatClient in Spring AI&lt;/strong&gt;, understand how the two abstractions work together, and build practical examples using different AI providers.&lt;/p&gt;




&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is Spring AI?
&lt;/h3&gt;

&lt;p&gt;Spring AI is a framework for integrating AI models and AI application capabilities into Spring applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is Spring AI only for OpenAI?
&lt;/h3&gt;

&lt;p&gt;No. The course material covers multiple providers and approaches, including OpenAI, Ollama, Docker Model Runner, and AWS Bedrock.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need Python to learn Spring AI?
&lt;/h3&gt;

&lt;p&gt;No. This series focuses on Java and Spring Boot.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need to train my own AI model?
&lt;/h3&gt;

&lt;p&gt;No. Spring AI is primarily about integrating existing AI models into applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can Spring AI work with my existing backend?
&lt;/h3&gt;

&lt;p&gt;Yes. That is one of the main reasons it is useful for Spring developers. AI capabilities can be added alongside existing APIs, databases, services, and business logic.&lt;/p&gt;

&lt;h3&gt;
  
  
  What will I learn after the basics?
&lt;/h3&gt;

&lt;p&gt;The series will progressively cover prompts, memory, embeddings, RAG, vector stores, tool calling, MCP, evaluation, observability, and more.&lt;/p&gt;




&lt;h2&gt;
  
  
  Series Navigation
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Part 1:&lt;/strong&gt; Spring AI Tutorial: How Java Developers Can Build Generative AI Applications with Spring Boot&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 2:&lt;/strong&gt; ChatModel vs ChatClient in Spring AI&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 3:&lt;/strong&gt; Build Your First Spring AI Application with OpenAI&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 4:&lt;/strong&gt; Run Local LLMs with Ollama and Spring AI&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 5:&lt;/strong&gt; Run AI Models Locally with Docker Model Runner&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 6:&lt;/strong&gt; Using AWS Bedrock with Spring AI&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 7:&lt;/strong&gt; Understanding Multiple Chat Models in Spring AI&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 8:&lt;/strong&gt; Understanding AI Message Roles&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 9:&lt;/strong&gt; System Messages and User Messages in Spring AI&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 10:&lt;/strong&gt; Default Configuration in Spring AI ChatClient&lt;/p&gt;

&lt;p&gt;The series will then move into prompt engineering, structured output, LLM fundamentals, tokens, embeddings, memory, RAG, vector databases, tool calling, MCP, evaluation, observability, and AI agents.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>springboot</category>
      <category>openai</category>
      <category>java</category>
    </item>
    <item>
      <title>Java Spring Boot Logging: Log Levels, Logback, JSON Logs &amp; Production Best Practices</title>
      <dc:creator>Ayush Shrivastava</dc:creator>
      <pubDate>Sat, 08 Aug 2026 06:12:32 +0000</pubDate>
      <link>https://dev.to/ayshriv/java-spring-boot-logging-log-levels-logback-json-logs-production-best-practices-3f7o</link>
      <guid>https://dev.to/ayshriv/java-spring-boot-logging-log-levels-logback-json-logs-production-best-practices-3f7o</guid>
      <description>&lt;h1&gt;
  
  
  Production-Grade Logging in Spring Boot: A Complete Guide to Logging Levels, Files, JSON Logs, Correlation IDs, and Best Practices
&lt;/h1&gt;

&lt;p&gt;Logging is one of the most important parts of a production backend system.&lt;/p&gt;

&lt;p&gt;When everything works, you may not think much about logs.&lt;/p&gt;

&lt;p&gt;But when production starts returning 500 errors at 2 AM, a customer reports that an API is failing, or a payment request behaves unexpectedly, logs become one of your most important debugging tools.&lt;/p&gt;

&lt;p&gt;Poor logging makes production debugging painful.&lt;/p&gt;

&lt;p&gt;Good logging helps you answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What happened?&lt;/li&gt;
&lt;li&gt;When did it happen?&lt;/li&gt;
&lt;li&gt;Which user triggered it?&lt;/li&gt;
&lt;li&gt;Which request caused it?&lt;/li&gt;
&lt;li&gt;Which service handled it?&lt;/li&gt;
&lt;li&gt;How long did it take?&lt;/li&gt;
&lt;li&gt;What failed?&lt;/li&gt;
&lt;li&gt;Why did it fail?&lt;/li&gt;
&lt;li&gt;What should we investigate next?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this article, we will build a production-grade logging strategy for a &lt;strong&gt;Java Spring Boot application&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  1. What Does Production-Grade Logging Mean?
&lt;/h1&gt;

&lt;p&gt;Production-grade logging is not simply:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"User created"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Production logging should be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Structured&lt;/li&gt;
&lt;li&gt;Searchable&lt;/li&gt;
&lt;li&gt;Consistent&lt;/li&gt;
&lt;li&gt;Secure&lt;/li&gt;
&lt;li&gt;Configurable&lt;/li&gt;
&lt;li&gt;Environment-aware&lt;/li&gt;
&lt;li&gt;Correlated across requests&lt;/li&gt;
&lt;li&gt;Useful during debugging&lt;/li&gt;
&lt;li&gt;Suitable for monitoring and alerting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A good logging architecture might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spring Boot Application
        |
        v
     Logback
        |
        +---- application.log
        |
        +---- error.log
        |
        +---- audit.log
        |
        +---- access.log
        |
        v
Log Aggregation
        |
        +---- ELK
        +---- Grafana Loki
        +---- CloudWatch
        +---- Datadog
        +---- Splunk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The goal is not to log everything.&lt;/p&gt;

&lt;p&gt;The goal is to log the &lt;strong&gt;right information at the right level&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  2. Understanding Log Levels
&lt;/h1&gt;

&lt;p&gt;Spring Boot uses SLF4J as the logging abstraction and commonly uses Logback as the underlying logging implementation.&lt;/p&gt;

&lt;p&gt;The most common log levels are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TRACE
DEBUG
INFO
WARN
ERROR
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The order represents increasing severity.&lt;/p&gt;




&lt;h2&gt;
  
  
  TRACE
&lt;/h2&gt;

&lt;p&gt;TRACE is the most detailed logging level.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;trace&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Entering calculateInvoice() with customerId={}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;customerId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use TRACE for very detailed diagnostic information.&lt;/p&gt;

&lt;p&gt;Usually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Production: OFF
Development: Sometimes ON
Debugging: Useful
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Avoid keeping TRACE enabled globally in production because it can generate huge amounts of logs.&lt;/p&gt;




&lt;h1&gt;
  
  
  3. DEBUG
&lt;/h1&gt;

&lt;p&gt;DEBUG is useful for developers.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Fetching customer with customerId={}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;customerId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Payment request received for orderId={}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;DEBUG logs are useful when troubleshooting a specific feature.&lt;/p&gt;

&lt;p&gt;A common production strategy is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INFO  -&amp;gt; Default
DEBUG -&amp;gt; Temporarily enabled when troubleshooting
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  4. INFO
&lt;/h1&gt;

&lt;p&gt;INFO should contain important application events.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"User successfully created. userId={}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Order successfully created. orderId={}, customerId={}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;customerId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Good INFO logs might include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application started
User registered
Order created
Payment completed
File uploaded
Scheduled job completed
External integration connected
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But don't log every line of your application at INFO.&lt;/p&gt;




&lt;h1&gt;
  
  
  5. WARN
&lt;/h1&gt;

&lt;p&gt;WARN indicates something unexpected or potentially problematic.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;warn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Login attempt failed. email={}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;warn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Payment provider response time is high. durationMs={}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;durationMs&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;WARN means:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The application is still functioning, but someone should pay attention."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Retry occurred&lt;/li&gt;
&lt;li&gt;External API is slow&lt;/li&gt;
&lt;li&gt;Deprecated API was called&lt;/li&gt;
&lt;li&gt;Configuration is missing but has a fallback&lt;/li&gt;
&lt;li&gt;Login failed repeatedly&lt;/li&gt;
&lt;li&gt;Database connection pool is close to its limit&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  6. ERROR
&lt;/h1&gt;

&lt;p&gt;ERROR represents a failure that needs investigation.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to create order. orderId={}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that the exception is passed separately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to create order"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to create order "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getMessage&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first approach preserves the stack trace.&lt;/p&gt;




&lt;h1&gt;
  
  
  7. Never Log Sensitive Information
&lt;/h1&gt;

&lt;p&gt;One of the most important production logging rules is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Logs should never become a source of sensitive data leakage.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Never log:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Passwords
Access tokens
Refresh tokens
API keys
Credit card numbers
CVV
Session IDs
Private keys
Authorization headers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bad:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Login request: username={}, password={}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Good:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Login attempt received. username={}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even better, depending on your privacy requirements, avoid logging email addresses or other personal identifiers unless there is a clear operational reason.&lt;/p&gt;




&lt;h1&gt;
  
  
  8. Use Parameterized Logging
&lt;/h1&gt;

&lt;p&gt;Avoid string concatenation.&lt;/p&gt;

&lt;p&gt;Don't do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"User created: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Prefer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"User created. userId={}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For multiple values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"Order created. orderId={}, customerId={}, amount={}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;customerId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;amount&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Parameterized logging is cleaner and avoids unnecessary string construction.&lt;/p&gt;




&lt;h1&gt;
  
  
  9. Create a Centralized Logging Configuration
&lt;/h1&gt;

&lt;p&gt;Spring Boot makes it easy to configure Logback.&lt;/p&gt;

&lt;p&gt;You can create:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/main/resources/logback-spring.xml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A basic configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;property&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"LOG_DIR"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"./logs"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;appender&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"CONSOLE"&lt;/span&gt;
              &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"ch.qos.logback.core.ConsoleAppender"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

        &lt;span class="nt"&gt;&amp;lt;encoder&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;pattern&amp;gt;&lt;/span&gt;
                %d{yyyy-MM-dd HH:mm:ss.SSS}
                [%thread]
                %-5level
                %logger{36}
                -
                %msg%n
            &lt;span class="nt"&gt;&amp;lt;/pattern&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/encoder&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;/appender&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;root&lt;/span&gt; &lt;span class="na"&gt;level=&lt;/span&gt;&lt;span class="s"&gt;"INFO"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;appender-ref&lt;/span&gt; &lt;span class="na"&gt;ref=&lt;/span&gt;&lt;span class="s"&gt;"CONSOLE"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/root&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the application produces readable logs such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2026-08-08 10:30:25.123 [http-nio-8080-exec-1] INFO
c.example.user.UserService -
User created. userId=123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  10. Different Log Files for Different Levels
&lt;/h1&gt;

&lt;p&gt;For a production application, you may want separate files.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;logs/
├── application.log
├── error.log
├── audit.log
└── access.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes troubleshooting easier.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;application.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;contains general application events.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;contains ERROR events.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;audit.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;contains important security/business events.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;access.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;contains HTTP request information.&lt;/p&gt;




&lt;h1&gt;
  
  
  11. Creating an ERROR Log File
&lt;/h1&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;appender&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"ERROR_FILE"&lt;/span&gt;
          &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"ch.qos.logback.core.rolling.RollingFileAppender"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;file&amp;gt;&lt;/span&gt;${LOG_DIR}/error.log&lt;span class="nt"&gt;&amp;lt;/file&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;rollingPolicy&lt;/span&gt;
        &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

        &lt;span class="nt"&gt;&amp;lt;fileNamePattern&amp;gt;&lt;/span&gt;
            ${LOG_DIR}/archive/error.%d{yyyy-MM-dd}.%i.log.gz
        &lt;span class="nt"&gt;&amp;lt;/fileNamePattern&amp;gt;&lt;/span&gt;

        &lt;span class="nt"&gt;&amp;lt;maxFileSize&amp;gt;&lt;/span&gt;100MB&lt;span class="nt"&gt;&amp;lt;/maxFileSize&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;maxHistory&amp;gt;&lt;/span&gt;30&lt;span class="nt"&gt;&amp;lt;/maxHistory&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;totalSizeCap&amp;gt;&lt;/span&gt;5GB&lt;span class="nt"&gt;&amp;lt;/totalSizeCap&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;/rollingPolicy&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;filter&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"ch.qos.logback.classic.filter.ThresholdFilter"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;level&amp;gt;&lt;/span&gt;ERROR&lt;span class="nt"&gt;&amp;lt;/level&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/filter&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;encoder&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;pattern&amp;gt;&lt;/span&gt;
            %d{yyyy-MM-dd HH:mm:ss.SSS}
            [%thread]
            %-5level
            %logger{36}
            -
            %msg%n
        &lt;span class="nt"&gt;&amp;lt;/pattern&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/encoder&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/appender&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now ERROR logs can be stored separately.&lt;/p&gt;




&lt;h1&gt;
  
  
  12. Why Log Rotation Matters
&lt;/h1&gt;

&lt;p&gt;Imagine your application generates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;application.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and you never rotate it.&lt;/p&gt;

&lt;p&gt;After several months:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;application.log = 150 GB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your server's disk eventually becomes full.&lt;/p&gt;

&lt;p&gt;This can cause much bigger problems.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application
    |
    v
Disk full
    |
    +---- Logging fails
    +---- Database operations may fail
    +---- Temporary files cannot be created
    +---- Application becomes unstable
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's why production applications need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Maximum file size&lt;/li&gt;
&lt;li&gt;Maximum history&lt;/li&gt;
&lt;li&gt;Total storage limit&lt;/li&gt;
&lt;li&gt;Compression&lt;/li&gt;
&lt;li&gt;Time-based rotation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;maxFileSize&amp;gt;&lt;/span&gt;100MB&lt;span class="nt"&gt;&amp;lt;/maxFileSize&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;maxHistory&amp;gt;&lt;/span&gt;30&lt;span class="nt"&gt;&amp;lt;/maxHistory&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;totalSizeCap&amp;gt;&lt;/span&gt;5GB&lt;span class="nt"&gt;&amp;lt;/totalSizeCap&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  13. Application Logs vs Audit Logs
&lt;/h1&gt;

&lt;p&gt;Not every important event is an application error.&lt;/p&gt;

&lt;p&gt;Consider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Admin disabled user 123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This isn't an ERROR.&lt;/p&gt;

&lt;p&gt;It is an &lt;strong&gt;audit event&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Create a separate audit logger.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Logger&lt;/span&gt; &lt;span class="n"&gt;auditLogger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="nc"&gt;LoggerFactory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getLogger&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AUDIT"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;auditLogger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"User disabled. adminId={}, targetUserId={}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;adminId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;targetUserId&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you a separate audit stream.&lt;/p&gt;




&lt;h1&gt;
  
  
  14. Audit Logging Is Extremely Important
&lt;/h1&gt;

&lt;p&gt;For systems involving multiple users, administrators, payments, permissions, or sensitive operations, audit logging becomes extremely valuable.&lt;/p&gt;

&lt;p&gt;Examples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;USER_CREATED
USER_DISABLED
USER_ENABLED
PASSWORD_CHANGED
ROLE_CHANGED
LOGIN_SUCCESS
LOGIN_FAILED
API_KEY_CREATED
API_KEY_REVOKED
DATA_EXPORTED
PAYMENT_COMPLETED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Something happened"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use structured information:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;auditLogger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"AUDIT event=ROLE_CHANGED actorId={} targetUserId={} oldRole={} newRole={}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;actorId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;targetUserId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;oldRole&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;newRole&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the event can easily be searched.&lt;/p&gt;




&lt;h1&gt;
  
  
  15. Logging HTTP Requests
&lt;/h1&gt;

&lt;p&gt;For backend systems, request logging is extremely useful.&lt;/p&gt;

&lt;p&gt;You want to know:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP Method
URL
Status Code
Execution Time
Request ID
User ID
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /api/users/123
status=200
duration=45ms
requestId=9f7a2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A servlet filter is one approach.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Component&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RequestLoggingFilter&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;OncePerRequestFilter&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Logger&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
            &lt;span class="nc"&gt;LoggerFactory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getLogger&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RequestLoggingFilter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;protected&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;doFilterInternal&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;HttpServletRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="nc"&gt;HttpServletResponse&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="nc"&gt;FilterChain&lt;/span&gt; &lt;span class="n"&gt;filterChain&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;ServletException&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;IOException&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;currentTimeMillis&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;filterChain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;doFilter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

            &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;duration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
                    &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;currentTimeMillis&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

            &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"HTTP request method={} uri={} status={} durationMs={}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getMethod&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
                &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getRequestURI&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
                &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getStatus&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
                &lt;span class="n"&gt;duration&lt;/span&gt;
            &lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you a basic access log.&lt;/p&gt;




&lt;h1&gt;
  
  
  16. Correlation IDs
&lt;/h1&gt;

&lt;p&gt;This is one of the most useful concepts in distributed systems.&lt;/p&gt;

&lt;p&gt;Imagine a request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Frontend
   |
   v
API Gateway
   |
   v
User Service
   |
   v
Payment Service
   |
   v
Notification Service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One request could generate dozens of logs.&lt;/p&gt;

&lt;p&gt;How do you identify which logs belong to the same request?&lt;/p&gt;

&lt;p&gt;Use a:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Correlation ID
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;requestId=7f83ab29
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then every service logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;requestId=7f83ab29
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can search the entire system using that ID.&lt;/p&gt;




&lt;h1&gt;
  
  
  17. Implementing Correlation ID with MDC
&lt;/h1&gt;

&lt;p&gt;SLF4J provides MDC:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="no"&gt;MDC&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"requestId"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;requestId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Component&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CorrelationIdFilter&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;OncePerRequestFilter&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="no"&gt;REQUEST_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"requestId"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;protected&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;doFilterInternal&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;HttpServletRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="nc"&gt;HttpServletResponse&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="nc"&gt;FilterChain&lt;/span&gt; &lt;span class="n"&gt;filterChain&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;ServletException&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;IOException&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;requestId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getHeader&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;REQUEST_ID&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;requestId&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;requestId&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isBlank&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;requestId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;UUID&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;randomUUID&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;

        &lt;span class="no"&gt;MDC&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;REQUEST_ID&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;requestId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setHeader&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;REQUEST_ID&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;requestId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;filterChain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;doFilter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="no"&gt;MDC&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;remove&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;REQUEST_ID&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now add it to Logback:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;pattern&amp;gt;&lt;/span&gt;
    %d{yyyy-MM-dd HH:mm:ss.SSS}
    [%thread]
    %-5level
    [%X{requestId}]
    %logger{36}
    -
    %msg%n
&lt;span class="nt"&gt;&amp;lt;/pattern&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The resulting log becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2026-08-08 12:20:10.120
[http-nio-8080-exec-2]
INFO
[7f83ab29]
UserService -
Fetching user userId=123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is much easier to debug.&lt;/p&gt;




&lt;h1&gt;
  
  
  18. Structured Logging
&lt;/h1&gt;

&lt;p&gt;Traditional logs look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User created successfully userId=123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Structured logs can look like JSON:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-08-08T12:20:10.120Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"INFO"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"service"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user-service"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"requestId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"7f83ab29"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"userId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"event"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"USER_CREATED"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is much easier for log aggregation systems to process.&lt;/p&gt;

&lt;p&gt;For production environments, structured JSON logging is often preferable.&lt;/p&gt;




&lt;h1&gt;
  
  
  19. Why JSON Logs Are Better for Production
&lt;/h1&gt;

&lt;p&gt;Suppose you use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ELK
Grafana Loki
Datadog
AWS CloudWatch
Splunk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can query structured fields.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;level = ERROR
service = payment-service
environment = production
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;requestId = 7f83ab29
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;durationMs &amp;gt; 1000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This becomes much more powerful than searching plain text.&lt;/p&gt;




&lt;h1&gt;
  
  
  20. Logging Exceptions Correctly
&lt;/h1&gt;

&lt;p&gt;Bad:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;paymentService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;process&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Payment failed: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getMessage&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This loses the stack trace.&lt;/p&gt;

&lt;p&gt;Better:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;paymentService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;process&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"Payment processing failed. paymentId={}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;paymentId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;e&lt;/span&gt;
    &lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ERROR Payment processing failed. paymentId=123

java.lang.IllegalStateException: Payment provider timeout
    at PaymentService.process(...)
    at PaymentController.create(...)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The stack trace is extremely important for debugging.&lt;/p&gt;




&lt;h1&gt;
  
  
  21. Don't Log the Same Exception Multiple Times
&lt;/h1&gt;

&lt;p&gt;A common mistake is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Repository
   ↓
Service
   ↓
Controller
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every layer catches and logs the same exception.&lt;/p&gt;

&lt;p&gt;You might get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ERROR Database failure
ERROR Service failure
ERROR Controller failure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three logs for one problem.&lt;/p&gt;

&lt;p&gt;Prefer centralized exception handling when possible.&lt;/p&gt;

&lt;p&gt;For Spring Boot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@RestControllerAdvice&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GlobalExceptionHandler&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Logger&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
            &lt;span class="nc"&gt;LoggerFactory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getLogger&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;GlobalExceptionHandler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="nd"&gt;@ExceptionHandler&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;ResponseEntity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;?&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;handleException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"Unhandled application exception"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;exception&lt;/span&gt;
        &lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;ResponseEntity&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;internalServerError&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Something went wrong"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now unexpected exceptions can be logged centrally.&lt;/p&gt;




&lt;h1&gt;
  
  
  22. Logging Business Events
&lt;/h1&gt;

&lt;p&gt;Not every useful log is technical.&lt;/p&gt;

&lt;p&gt;Business events can be extremely valuable.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"Order completed. orderId={}, customerId={}, amount={}, currency={}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;customerId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;currency&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This can help answer questions such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;How many orders were completed?
Which payment failed?
How long does checkout take?
Which customer experienced the problem?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Logging should help both developers and operations teams.&lt;/p&gt;




&lt;h1&gt;
  
  
  23. Logging External API Calls
&lt;/h1&gt;

&lt;p&gt;Suppose your application calls:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Stripe
Salesforce
OpenAI
AWS
Google Maps
Email provider
SMS provider
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should log useful metadata.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;currentTimeMillis&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nc"&gt;PaymentResponse&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
            &lt;span class="n"&gt;paymentClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createPayment&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;duration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
            &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;currentTimeMillis&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"Payment provider call completed. provider={} status={} durationMs={}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"stripe"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;duration&lt;/span&gt;
    &lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;duration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
            &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;currentTimeMillis&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"Payment provider call failed. provider={} durationMs={}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"stripe"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;duration&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;e&lt;/span&gt;
    &lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But never log:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Authorization header
API key
Access token
Full card details
Sensitive request payload
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  24. Logging Database Operations
&lt;/h1&gt;

&lt;p&gt;Don't log every SQL query in production unless you have a specific reason.&lt;/p&gt;

&lt;p&gt;For example, enabling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;spring.jpa.show-sql&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;in production can create huge amounts of output.&lt;/p&gt;

&lt;p&gt;For development:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;spring.jpa.show-sql&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;may be useful.&lt;/p&gt;

&lt;p&gt;For production:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;spring.jpa.show-sql&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead, monitor slow queries through proper database monitoring and profiling tools.&lt;/p&gt;




&lt;h1&gt;
  
  
  25. Different Logging Configuration Per Environment
&lt;/h1&gt;

&lt;p&gt;Your logging configuration should change based on the environment.&lt;/p&gt;

&lt;p&gt;Development:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DEBUG
Readable console logs
More diagnostic information
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Production:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INFO
JSON logs
Error tracking
Structured fields
Log rotation
Centralized log collection
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;spring&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;profiles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;active&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dev&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can maintain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;application-dev.yml
application-prod.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And configure logging accordingly.&lt;/p&gt;




&lt;h1&gt;
  
  
  26. Production Logging Architecture
&lt;/h1&gt;

&lt;p&gt;A practical production architecture might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                  Spring Boot
                      |
                      v
                   Logback
                      |
        +-------------+-------------+
        |             |             |
        v             v             v
   Application     Error        Audit
      Logs          Logs         Logs
        |             |             |
        +-------------+-------------+
                      |
                      v
                Log Collector
                      |
        +-------------+-------------+
        |             |             |
        v             v             v
   CloudWatch       Loki          ELK
                      |
                      v
                  Dashboard
                      |
                      v
                   Alerts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is much better than simply SSHing into a server and running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; application.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;every time something breaks.&lt;/p&gt;




&lt;h1&gt;
  
  
  27. Docker and Kubernetes Logging
&lt;/h1&gt;

&lt;p&gt;If your application runs inside Docker or Kubernetes, writing logs only to local files may not be the best strategy.&lt;/p&gt;

&lt;p&gt;A common approach is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application
     |
     v
stdout / stderr
     |
     v
Docker / Kubernetes
     |
     v
Log Collector
     |
     v
Centralized Logging Platform
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spring Boot
    ↓
stdout
    ↓
Docker
    ↓
Fluent Bit
    ↓
Elasticsearch
    ↓
Kibana
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows logs to remain available even when containers are recreated.&lt;/p&gt;




&lt;h1&gt;
  
  
  28. Logging in Kubernetes
&lt;/h1&gt;

&lt;p&gt;In Kubernetes, pods are disposable.&lt;/p&gt;

&lt;p&gt;That means this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pod A
  |
  +--- application.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is not necessarily a reliable long-term logging strategy.&lt;/p&gt;

&lt;p&gt;Instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pod
 |
 v
stdout
 |
 v
Container Runtime
 |
 v
Log Collector
 |
 v
Centralized Storage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is generally more suitable for cloud-native applications.&lt;/p&gt;




&lt;h1&gt;
  
  
  29. Log Levels Should Be Intentional
&lt;/h1&gt;

&lt;p&gt;A useful rule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TRACE → Extremely detailed diagnostics

DEBUG → Developer troubleshooting

INFO  → Important application events

WARN  → Unexpected but recoverable situation

ERROR → Failure requiring investigation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"User logged in successfully"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"User login successful. userId={}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Log levels should communicate severity.&lt;/p&gt;




&lt;h1&gt;
  
  
  30. Don't Log Everything
&lt;/h1&gt;

&lt;p&gt;More logs do not automatically mean better observability.&lt;/p&gt;

&lt;p&gt;Bad:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Starting method"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Entering service"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Repository called"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Repository returned"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Service completed"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Controller completed"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates noise.&lt;/p&gt;

&lt;p&gt;Instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"User profile updated. userId={} durationMs={}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;durationMs&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Log meaningful events.&lt;/p&gt;




&lt;h1&gt;
  
  
  31. Logging Performance
&lt;/h1&gt;

&lt;p&gt;Logging can affect application performance.&lt;/p&gt;

&lt;p&gt;Especially dangerous:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"Huge object: {}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;objectWithThousandsOfFields&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When DEBUG isn't enabled, parameterized logging helps avoid unnecessary string concatenation, but object serialization or expensive argument computation can still cost time.&lt;/p&gt;

&lt;p&gt;Avoid:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Response: {}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expensiveMethod&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;if the computation itself is expensive.&lt;/p&gt;

&lt;p&gt;You can guard expensive operations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isDebugEnabled&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Detailed response: {}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expensiveMethod&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use this only when the computation is genuinely expensive.&lt;/p&gt;




&lt;h1&gt;
  
  
  32. Async Logging
&lt;/h1&gt;

&lt;p&gt;High-throughput applications can benefit from asynchronous logging.&lt;/p&gt;

&lt;p&gt;Instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application
    |
    v
Write log
    |
    v
Disk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you can use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application
    |
    v
Async Queue
    |
    v
Logger
    |
    v
Disk / Collector
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This reduces the amount of time application threads spend waiting on logging operations.&lt;/p&gt;

&lt;p&gt;However, asynchronous logging should be configured carefully to avoid losing logs during abrupt shutdowns and to prevent queue overflow.&lt;/p&gt;




&lt;h1&gt;
  
  
  33. Log Retention
&lt;/h1&gt;

&lt;p&gt;Production logs should have a retention policy.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application logs → 30 days
Audit logs       → 90 days
Security logs    → 180 days
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The actual retention period should be based on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Compliance&lt;/li&gt;
&lt;li&gt;Security requirements&lt;/li&gt;
&lt;li&gt;Business requirements&lt;/li&gt;
&lt;li&gt;Storage cost&lt;/li&gt;
&lt;li&gt;Incident investigation needs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Don't keep everything forever.&lt;/p&gt;




&lt;h1&gt;
  
  
  34. A Practical Logback Configuration
&lt;/h1&gt;

&lt;p&gt;A simplified production configuration could look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;property&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"LOG_DIR"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"./logs"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;appender&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"CONSOLE"&lt;/span&gt;
              &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"ch.qos.logback.core.ConsoleAppender"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

        &lt;span class="nt"&gt;&amp;lt;encoder&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;pattern&amp;gt;&lt;/span&gt;
                %d{yyyy-MM-dd'T'HH:mm:ss.SSS}
                %-5level
                [%X{requestId}]
                %logger{36}
                -
                %msg%n
            &lt;span class="nt"&gt;&amp;lt;/pattern&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/encoder&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;/appender&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;appender&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"APPLICATION_FILE"&lt;/span&gt;
              &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"ch.qos.logback.core.rolling.RollingFileAppender"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

        &lt;span class="nt"&gt;&amp;lt;file&amp;gt;&lt;/span&gt;${LOG_DIR}/application.log&lt;span class="nt"&gt;&amp;lt;/file&amp;gt;&lt;/span&gt;

        &lt;span class="nt"&gt;&amp;lt;rollingPolicy&lt;/span&gt;
            &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

            &lt;span class="nt"&gt;&amp;lt;fileNamePattern&amp;gt;&lt;/span&gt;
                ${LOG_DIR}/archive/application.%d{yyyy-MM-dd}.%i.log.gz
            &lt;span class="nt"&gt;&amp;lt;/fileNamePattern&amp;gt;&lt;/span&gt;

            &lt;span class="nt"&gt;&amp;lt;maxFileSize&amp;gt;&lt;/span&gt;100MB&lt;span class="nt"&gt;&amp;lt;/maxFileSize&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;maxHistory&amp;gt;&lt;/span&gt;30&lt;span class="nt"&gt;&amp;lt;/maxHistory&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;totalSizeCap&amp;gt;&lt;/span&gt;5GB&lt;span class="nt"&gt;&amp;lt;/totalSizeCap&amp;gt;&lt;/span&gt;

        &lt;span class="nt"&gt;&amp;lt;/rollingPolicy&amp;gt;&lt;/span&gt;

        &lt;span class="nt"&gt;&amp;lt;encoder&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;pattern&amp;gt;&lt;/span&gt;
                %d{yyyy-MM-dd HH:mm:ss.SSS}
                %-5level
                [%X{requestId}]
                %logger{36}
                -
                %msg%n
            &lt;span class="nt"&gt;&amp;lt;/pattern&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/encoder&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;/appender&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;appender&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"ERROR_FILE"&lt;/span&gt;
              &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"ch.qos.logback.core.rolling.RollingFileAppender"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

        &lt;span class="nt"&gt;&amp;lt;file&amp;gt;&lt;/span&gt;${LOG_DIR}/error.log&lt;span class="nt"&gt;&amp;lt;/file&amp;gt;&lt;/span&gt;

        &lt;span class="nt"&gt;&amp;lt;filter&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"ch.qos.logback.classic.filter.ThresholdFilter"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;level&amp;gt;&lt;/span&gt;ERROR&lt;span class="nt"&gt;&amp;lt;/level&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/filter&amp;gt;&lt;/span&gt;

        &lt;span class="nt"&gt;&amp;lt;rollingPolicy&lt;/span&gt;
            &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

            &lt;span class="nt"&gt;&amp;lt;fileNamePattern&amp;gt;&lt;/span&gt;
                ${LOG_DIR}/archive/error.%d{yyyy-MM-dd}.%i.log.gz
            &lt;span class="nt"&gt;&amp;lt;/fileNamePattern&amp;gt;&lt;/span&gt;

            &lt;span class="nt"&gt;&amp;lt;maxFileSize&amp;gt;&lt;/span&gt;100MB&lt;span class="nt"&gt;&amp;lt;/maxFileSize&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;maxHistory&amp;gt;&lt;/span&gt;30&lt;span class="nt"&gt;&amp;lt;/maxHistory&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;totalSizeCap&amp;gt;&lt;/span&gt;2GB&lt;span class="nt"&gt;&amp;lt;/totalSizeCap&amp;gt;&lt;/span&gt;

        &lt;span class="nt"&gt;&amp;lt;/rollingPolicy&amp;gt;&lt;/span&gt;

        &lt;span class="nt"&gt;&amp;lt;encoder&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;pattern&amp;gt;&lt;/span&gt;
                %d{yyyy-MM-dd HH:mm:ss.SSS}
                %-5level
                [%X{requestId}]
                %logger{36}
                -
                %msg%n
            &lt;span class="nt"&gt;&amp;lt;/pattern&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/encoder&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;/appender&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;root&lt;/span&gt; &lt;span class="na"&gt;level=&lt;/span&gt;&lt;span class="s"&gt;"INFO"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

        &lt;span class="nt"&gt;&amp;lt;appender-ref&lt;/span&gt; &lt;span class="na"&gt;ref=&lt;/span&gt;&lt;span class="s"&gt;"CONSOLE"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;appender-ref&lt;/span&gt; &lt;span class="na"&gt;ref=&lt;/span&gt;&lt;span class="s"&gt;"APPLICATION_FILE"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;appender-ref&lt;/span&gt; &lt;span class="na"&gt;ref=&lt;/span&gt;&lt;span class="s"&gt;"ERROR_FILE"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;/root&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;logs/
├── application.log
├── error.log
└── archive/
    ├── application.2026-08-08.0.log.gz
    ├── error.2026-08-08.0.log.gz
    └── ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  35. Real-World Example
&lt;/h1&gt;

&lt;p&gt;Imagine a customer reports:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"My payment failed."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Without structured logging, you might search:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;payment failed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and find thousands of results.&lt;/p&gt;

&lt;p&gt;With production-grade logging, you can search:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;requestId=7f83ab29
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you might see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INFO  requestId=7f83ab29
Payment request received. orderId=ORD-123

INFO  requestId=7f83ab29
Calling payment provider. provider=stripe

WARN  requestId=7f83ab29
Payment provider response slow. durationMs=4200

ERROR requestId=7f83ab29
Payment provider call failed. orderId=ORD-123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you know exactly what happened.&lt;/p&gt;

&lt;p&gt;That's the real value of production logging.&lt;/p&gt;




&lt;h1&gt;
  
  
  36. Logging Best Practices Checklist
&lt;/h1&gt;

&lt;p&gt;Before deploying a Spring Boot application, check:&lt;/p&gt;

&lt;h3&gt;
  
  
  Log levels
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ ] TRACE is disabled in production
[ ] DEBUG is used intentionally
[ ] INFO contains meaningful events
[ ] WARN represents recoverable problems
[ ] ERROR represents actual failures
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Security
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ ] Passwords are never logged
[ ] Tokens are never logged
[ ] API keys are never logged
[ ] Sensitive headers are never logged
[ ] Sensitive payloads are not logged
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Reliability
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ ] Log rotation is configured
[ ] Maximum file size is configured
[ ] Retention policy exists
[ ] Disk usage is monitored
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Observability
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ ] Request ID exists
[ ] Correlation ID exists
[ ] Important business events are logged
[ ] External API failures are logged
[ ] Slow operations can be identified
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Production
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ ] Structured logging is available
[ ] Logs can be centralized
[ ] Alerts can be created
[ ] Logs are searchable
[ ] Audit events are separated when required
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  37. What I Consider a Good Production Logging Strategy
&lt;/h1&gt;

&lt;p&gt;For a modern Spring Boot backend, my preferred baseline would be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spring Boot
     |
     +--- SLF4J
     |
     +--- Logback
     |
     +--- Structured JSON
     |
     +--- Request ID / Correlation ID
     |
     +--- INFO as default
     |
     +--- DEBUG for troubleshooting
     |
     +--- ERROR for failures
     |
     +--- Audit logging for important actions
     |
     +--- Log rotation / retention
     |
     +--- Centralized log aggregation
     |
     +--- Monitoring + Alerting
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For cloud deployments, I would generally prefer sending structured logs to a centralized platform rather than depending exclusively on local log files.&lt;/p&gt;




&lt;h1&gt;
  
  
  38. Final Thoughts
&lt;/h1&gt;

&lt;p&gt;Logging is not something you add at the end of development.&lt;/p&gt;

&lt;p&gt;It is part of backend architecture.&lt;/p&gt;

&lt;p&gt;A production-ready application should make it easy to understand:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;What happened?
When?
Where?
Who triggered it?
Which request?
Which service?
How long?
What failed?
Why?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The goal isn't to create millions of log lines.&lt;/p&gt;

&lt;p&gt;The goal is to create &lt;strong&gt;useful signals&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A good production logging system gives developers confidence when everything is working and, more importantly, gives them the information they need when something goes wrong.&lt;/p&gt;

&lt;p&gt;If your application is running in production, ask yourself:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"If this API fails at 3 AM, can I understand exactly what happened from the logs?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the answer is no, your logging strategy probably needs another iteration.&lt;/p&gt;

</description>
      <category>java</category>
      <category>ai</category>
      <category>springboot</category>
      <category>logging</category>
    </item>
    <item>
      <title>Caching with Redis and Spring Data Redis in Spring Boot</title>
      <dc:creator>Ayush Shrivastava</dc:creator>
      <pubDate>Wed, 29 Jul 2026 14:40:29 +0000</pubDate>
      <link>https://dev.to/ayshriv/caching-with-redis-and-spring-data-redis-in-spring-boot-4860</link>
      <guid>https://dev.to/ayshriv/caching-with-redis-and-spring-data-redis-in-spring-boot-4860</guid>
      <description>&lt;h1&gt;
  
  
  Caching with Redis and Spring Data Redis in Spring Boot
&lt;/h1&gt;

&lt;p&gt;Caching is one of the most effective ways to improve the performance and scalability of modern applications. Instead of querying the database for the same data repeatedly, you can temporarily store frequently accessed data in memory and serve it almost instantly.&lt;/p&gt;

&lt;p&gt;One of the most popular technologies for implementing caching is &lt;strong&gt;Redis&lt;/strong&gt;, an in-memory data store known for its speed, flexibility, and reliability.&lt;/p&gt;

&lt;p&gt;In this article, you'll learn:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What caching is&lt;/li&gt;
&lt;li&gt;Why Redis is an excellent caching solution&lt;/li&gt;
&lt;li&gt;How Spring Data Redis works&lt;/li&gt;
&lt;li&gt;How to configure Redis in Spring Boot&lt;/li&gt;
&lt;li&gt;How to use &lt;code&gt;@Cacheable&lt;/code&gt;, &lt;code&gt;@CachePut&lt;/code&gt;, and &lt;code&gt;@CacheEvict&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Best practices for production applications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's get started.&lt;/p&gt;




&lt;h1&gt;
  
  
  What is Caching?
&lt;/h1&gt;

&lt;p&gt;Caching is the process of storing copies of frequently accessed data in a fast storage layer (cache), so future requests can be served without querying the database.&lt;/p&gt;

&lt;p&gt;Without caching:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client
   │
   ▼
Spring Boot
   │
   ▼
Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every request reaches the database.&lt;/p&gt;

&lt;p&gt;With caching:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client
   │
   ▼
Spring Boot
   │
   ▼
Redis Cache
   │
 Cache Hit?
  │     │
 Yes    No
 │       │
 ▼       ▼
Return  Database
          │
          ▼
      Store in Redis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first request loads data from the database and stores it in Redis.&lt;/p&gt;

&lt;p&gt;Subsequent requests are served directly from Redis.&lt;/p&gt;

&lt;p&gt;This dramatically reduces latency and database load.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Use Redis for Caching?
&lt;/h1&gt;

&lt;p&gt;Redis is one of the fastest key-value stores available because it stores data entirely in memory.&lt;/p&gt;

&lt;p&gt;Some major advantages include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Extremely low latency&lt;/li&gt;
&lt;li&gt;High throughput&lt;/li&gt;
&lt;li&gt;Rich data structures&lt;/li&gt;
&lt;li&gt;Optional persistence&lt;/li&gt;
&lt;li&gt;Replication support&lt;/li&gt;
&lt;li&gt;Redis Cluster support&lt;/li&gt;
&lt;li&gt;Automatic expiration (TTL)&lt;/li&gt;
&lt;li&gt;Pub/Sub messaging&lt;/li&gt;
&lt;li&gt;Transactions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because of these features, Redis is widely used in high-performance systems.&lt;/p&gt;




&lt;h1&gt;
  
  
  What is Spring Data Redis?
&lt;/h1&gt;

&lt;p&gt;Spring Data Redis is part of the Spring Data ecosystem.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Easy Redis integration&lt;/li&gt;
&lt;li&gt;Repository support&lt;/li&gt;
&lt;li&gt;RedisTemplate API&lt;/li&gt;
&lt;li&gt;Spring Cache integration&lt;/li&gt;
&lt;li&gt;Serialization support&lt;/li&gt;
&lt;li&gt;Object mapping&lt;/li&gt;
&lt;li&gt;Connection management&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of writing Redis commands manually, Spring Boot handles most of the complexity for you.&lt;/p&gt;




&lt;h1&gt;
  
  
  Project Dependencies
&lt;/h1&gt;

&lt;p&gt;Add the following dependencies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependencies&amp;gt;&lt;/span&gt;

    &lt;span class="c"&gt;&amp;lt;!-- Spring Data Redis --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework.boot&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-boot-starter-data-redis&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;

    &lt;span class="c"&gt;&amp;lt;!-- Spring Cache --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework.boot&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-boot-starter-cache&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;

    &lt;span class="c"&gt;&amp;lt;!-- Jedis Client --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;redis.clients&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;jedis&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/dependencies&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Newer Spring Boot versions use &lt;strong&gt;Lettuce&lt;/strong&gt; as the default Redis client. Unless you specifically need Jedis, Lettuce is generally recommended for production.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  Running Redis
&lt;/h1&gt;

&lt;p&gt;Using Docker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; redis &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 6379:6379 &lt;span class="se"&gt;\&lt;/span&gt;
  redis:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify Redis is running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker ps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Configure Redis
&lt;/h1&gt;

&lt;p&gt;Create a configuration class.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Configuration&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RedisConfig&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Bean&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;RedisConnectionFactory&lt;/span&gt; &lt;span class="nf"&gt;redisConnectionFactory&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;JedisConnectionFactory&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Bean&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;RedisTemplate&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;redisTemplate&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="nc"&gt;RedisTemplate&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RedisTemplate&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;

        &lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setConnectionFactory&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;redisConnectionFactory&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

        &lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setKeySerializer&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StringRedisSerializer&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

        &lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setValueSerializer&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;GenericJackson2JsonRedisSerializer&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Configure application.properties
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;spring.data.redis.host&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;localhost&lt;/span&gt;
&lt;span class="py"&gt;spring.data.redis.port&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;6379&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If authentication is enabled:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;spring.data.redis.password&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;your-password&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Enable Caching
&lt;/h1&gt;

&lt;p&gt;Simply add &lt;code&gt;@EnableCaching&lt;/code&gt; to your Spring Boot application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@SpringBootApplication&lt;/span&gt;
&lt;span class="nd"&gt;@EnableCaching&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RedisCacheApplication&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;SpringApplication&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RedisCacheApplication&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now Spring Boot can automatically manage your cache.&lt;/p&gt;




&lt;h1&gt;
  
  
  Create the Product Entity
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@RedisHash&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Product"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Id&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// getters and setters&lt;/span&gt;

&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Create the Repository
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;ProductRepository&lt;/span&gt;
        &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;CrudRepository&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing else is required.&lt;/p&gt;

&lt;p&gt;Spring Data generates the implementation automatically.&lt;/p&gt;




&lt;h1&gt;
  
  
  Using Cache Annotations
&lt;/h1&gt;

&lt;p&gt;Spring provides three important annotations:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Annotation&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@Cacheable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Reads from cache before executing the method&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@CachePut&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Updates both database and cache&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@CacheEvict&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Removes data from cache&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h1&gt;
  
  
  Product Service
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Service&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Autowired&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;ProductRepository&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Cacheable&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"products"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"#id"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt; &lt;span class="nf"&gt;getProductById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Fetching from database..."&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;orElse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@CachePut&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"products"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"#product.id"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt; &lt;span class="nf"&gt;updateProduct&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Product&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@CacheEvict&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"products"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"#id"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;deleteProduct&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;deleteById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Understanding @Cacheable
&lt;/h1&gt;

&lt;p&gt;When the following method is called:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;productService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getProductById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Spring checks Redis.&lt;/p&gt;

&lt;h3&gt;
  
  
  If the key exists
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Redis
   │
Found
   │
Return cached object
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The database is never accessed.&lt;/p&gt;

&lt;h3&gt;
  
  
  If the key does not exist
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Database
   │
Load object
   │
Store in Redis
   │
Return object
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Future requests will use Redis.&lt;/p&gt;




&lt;h1&gt;
  
  
  Understanding @CachePut
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@CachePut&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"products"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"#product.id"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unlike &lt;code&gt;@Cacheable&lt;/code&gt;, this annotation &lt;strong&gt;always executes&lt;/strong&gt; the method.&lt;/p&gt;

&lt;p&gt;Workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Update Database

↓

Update Redis

↓

Return Updated Object
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures your cache remains synchronized.&lt;/p&gt;




&lt;h1&gt;
  
  
  Understanding @CacheEvict
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@CacheEvict&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"products"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"#id"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When deleting a product:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Delete Database Record

↓

Remove Redis Cache

↓

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

&lt;/div&gt;



&lt;p&gt;Without cache eviction, users could still receive stale data.&lt;/p&gt;




&lt;h1&gt;
  
  
  Testing the Cache
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Component&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CacheTestRunner&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;CommandLineRunner&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Autowired&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;ProductService&lt;/span&gt; &lt;span class="n"&gt;productService&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="nc"&gt;Product&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

        &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Laptop"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setPrice&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;999.99&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;productService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;updateProduct&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;productService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getProductById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;productService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getProductById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

        &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Gaming Laptop"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;productService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;updateProduct&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;productService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getProductById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

        &lt;span class="n"&gt;productService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;deleteProduct&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;productService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getProductById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Expected Output
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Fetching from database...

Product{id='1', name='Laptop'}

Product{id='1', name='Laptop'}

Product{id='1', name='Gaming Laptop'}

Fetching from database...

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

&lt;/div&gt;



&lt;p&gt;Notice that &lt;code&gt;"Fetching from database..."&lt;/code&gt; appears only when the cache misses.&lt;/p&gt;




&lt;h1&gt;
  
  
  Cache Lifecycle
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client Request
      │
      ▼
Check Redis
      │
 ┌────┴────┐
 │         │
Hit       Miss
 │         │
 ▼         ▼
Return   Query DB
            │
            ▼
      Save in Redis
            │
            ▼
      Return Response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Production Best Practices
&lt;/h1&gt;

&lt;h3&gt;
  
  
  1. Use TTL (Time-To-Live)
&lt;/h3&gt;

&lt;p&gt;Avoid keeping cached data forever.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;spring.cache.redis.time-to-live&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;10m&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  2. Cache Only Frequently Accessed Data
&lt;/h3&gt;

&lt;p&gt;Good candidates include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Product catalog&lt;/li&gt;
&lt;li&gt;User profiles&lt;/li&gt;
&lt;li&gt;Categories&lt;/li&gt;
&lt;li&gt;Settings&lt;/li&gt;
&lt;li&gt;Configuration&lt;/li&gt;
&lt;li&gt;Country lists&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Avoid caching highly volatile data unless necessary.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Use Meaningful Cache Names
&lt;/h3&gt;

&lt;p&gt;Instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"cache1"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Prefer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"products"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"users"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  4. Serialize JSON
&lt;/h3&gt;

&lt;p&gt;Using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;GenericJackson2JsonRedisSerializer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;makes cached objects easier to inspect and maintain.&lt;/p&gt;




&lt;h3&gt;
  
  
  5. Monitor Cache Performance
&lt;/h3&gt;

&lt;p&gt;Keep an eye on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cache hit rate&lt;/li&gt;
&lt;li&gt;Cache miss rate&lt;/li&gt;
&lt;li&gt;Memory usage&lt;/li&gt;
&lt;li&gt;Eviction count&lt;/li&gt;
&lt;li&gt;Response time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Monitoring helps optimize your caching strategy.&lt;/p&gt;




&lt;h1&gt;
  
  
  Common Cache Annotations
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Cacheable&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reads from cache.&lt;/p&gt;






&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@CachePut&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Updates cache.&lt;/p&gt;






&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@CacheEvict&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deletes cache.&lt;/p&gt;






&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Caching&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Combines multiple cache operations.&lt;/p&gt;






&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@CacheConfig&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Defines shared cache configuration.&lt;/p&gt;




&lt;h1&gt;
  
  
  Advantages of Redis Caching
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Faster response times&lt;/li&gt;
&lt;li&gt;Reduced database load&lt;/li&gt;
&lt;li&gt;Better scalability&lt;/li&gt;
&lt;li&gt;Improved user experience&lt;/li&gt;
&lt;li&gt;Lower infrastructure costs&lt;/li&gt;
&lt;li&gt;High availability&lt;/li&gt;
&lt;li&gt;Distributed caching support&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  When Should You Use Redis?
&lt;/h1&gt;

&lt;p&gt;Redis is an excellent choice when your application needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Frequently accessed data&lt;/li&gt;
&lt;li&gt;High read traffic&lt;/li&gt;
&lt;li&gt;Low latency&lt;/li&gt;
&lt;li&gt;Distributed caching&lt;/li&gt;
&lt;li&gt;Session storage&lt;/li&gt;
&lt;li&gt;API response caching&lt;/li&gt;
&lt;li&gt;Rate limiting&lt;/li&gt;
&lt;li&gt;Leaderboards&lt;/li&gt;
&lt;li&gt;Real-time analytics&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Caching is one of the simplest yet most impactful optimizations you can make in a Spring Boot application. By integrating &lt;strong&gt;Redis&lt;/strong&gt; with &lt;strong&gt;Spring Data Redis&lt;/strong&gt;, you can significantly reduce database load, improve response times, and build applications that scale efficiently under heavy traffic.&lt;/p&gt;

&lt;p&gt;Spring Boot's caching abstraction makes implementation straightforward with annotations like &lt;code&gt;@Cacheable&lt;/code&gt;, &lt;code&gt;@CachePut&lt;/code&gt;, and &lt;code&gt;@CacheEvict&lt;/code&gt;, allowing you to focus on business logic rather than cache management.&lt;/p&gt;

&lt;p&gt;Whether you're building a REST API, microservices architecture, or a high-traffic web application, Redis caching is a proven strategy to boost performance and deliver a better user experience.&lt;/p&gt;

&lt;p&gt;If you're working with Spring Boot in production, learning Redis is a skill that will pay dividends across many real-world applications.&lt;/p&gt;

&lt;p&gt;Happy Coding!&lt;/p&gt;

</description>
      <category>springboot</category>
      <category>redis</category>
      <category>java</category>
      <category>ayshriv</category>
    </item>
  </channel>
</rss>
