<?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>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>
    <item>
      <title>Building an AI-Powered Travel Planner with Spring AI and Azure OpenAI</title>
      <dc:creator>Ayush Shrivastava</dc:creator>
      <pubDate>Tue, 21 Jul 2026 08:56:23 +0000</pubDate>
      <link>https://dev.to/ayshriv/building-an-ai-powered-travel-planner-with-spring-ai-and-azure-openai-3idj</link>
      <guid>https://dev.to/ayshriv/building-an-ai-powered-travel-planner-with-spring-ai-and-azure-openai-3idj</guid>
      <description>&lt;p&gt;A complete step-by-step tutorial for building a production-ready REST API that generates personalized travel itineraries using AI.&lt;/p&gt;




&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;What We're Building&lt;/li&gt;
&lt;li&gt;Project Setup &amp;amp; Dependencies&lt;/li&gt;
&lt;li&gt;The Application Entry Point&lt;/li&gt;
&lt;li&gt;Request &amp;amp; Response Models (DTOs)&lt;/li&gt;
&lt;li&gt;Prompt Engineering with Spring AI&lt;/li&gt;
&lt;li&gt;The Service Layer — Core Business Logic&lt;/li&gt;
&lt;li&gt;The Controller Layer — REST API&lt;/li&gt;
&lt;li&gt;Exception Handling &amp;amp; Error Responses&lt;/li&gt;
&lt;li&gt;OpenAPI / Swagger Configuration&lt;/li&gt;
&lt;li&gt;Application Configuration&lt;/li&gt;
&lt;li&gt;Running the Application&lt;/li&gt;
&lt;li&gt;Testing the API&lt;/li&gt;
&lt;li&gt;Real-World Use Cases&lt;/li&gt;
&lt;li&gt;Troubleshooting Common Issues&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  1. What We're Building
&lt;/h2&gt;

&lt;p&gt;Imagine you're planning a trip to Goa. You have ₹25,000, 5 days, and you love beaches, food, and nightlife. Wouldn't it be great if an AI could instantly generate a complete day-by-day itinerary — including activities, meal suggestions, budget breakdown, packing checklists, and safety tips?&lt;/p&gt;

&lt;p&gt;That's exactly what this project does.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The AI Travel Planner&lt;/strong&gt; is a Spring Boot REST API that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Accepts your travel preferences (destination, days, budget, style, interests)&lt;/li&gt;
&lt;li&gt;Sends them to Azure OpenAI (GPT model)&lt;/li&gt;
&lt;li&gt;Returns a structured, realistic travel plan as JSON&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Real-world example:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A user planning a graduation trip with friends can send a request for "Goa, 5 days, ₹25,000, Budget style, interested in Beaches/Food/Nightlife" and get back a complete itinerary including scooter rental advice, beach shack recommendations, and safety tips — all within seconds.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  2. Project Setup &amp;amp; Dependencies
&lt;/h2&gt;

&lt;p&gt;Let's start with the foundation — the &lt;code&gt;pom.xml&lt;/code&gt; file. Think of this as the recipe card for your project; it tells Maven what ingredients (libraries) you need.&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;parent&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-parent&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;3.5.16&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/parent&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key Dependencies Explained:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dependency&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Real-World Analogy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spring-boot-starter-web&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Builds REST APIs&lt;/td&gt;
&lt;td&gt;Like having a restaurant kitchen — gives you all the tools to serve HTTP requests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spring-boot-starter-validation&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Validates user input&lt;/td&gt;
&lt;td&gt;Like a bouncer checking IDs at the door — ensures only valid data enters your system&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spring-ai-starter-model-azure-openai&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Connects to Azure OpenAI&lt;/td&gt;
&lt;td&gt;Like having a direct phone line to a genius travel agent who knows everything&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;springdoc-openapi-starter-webmvc-ui&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Auto-generates API docs&lt;/td&gt;
&lt;td&gt;Like a menu board showing all available dishes with descriptions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;lombok&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Reduces boilerplate code&lt;/td&gt;
&lt;td&gt;Like a personal assistant who writes your repetitive code for you&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spring-boot-starter-actuator&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Health monitoring&lt;/td&gt;
&lt;td&gt;Like a car's dashboard — shows you engine status, fuel level, etc.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Real-world scenario:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In a real startup, you might swap Azure OpenAI for OpenAI directly, or add Spring Security for authentication. The modular dependency design lets you swap components without rewriting code.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  3. The Application Entry Point
&lt;/h2&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="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SpringAiTravelPlannerApplication&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;SpringAiTravelPlannerApplication&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;This is the &lt;strong&gt;ignition switch&lt;/strong&gt; of your application. &lt;code&gt;@SpringBootApplication&lt;/code&gt; is a convenience annotation that combines three things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;@Configuration&lt;/strong&gt; — Marks this as a source of bean definitions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;@EnableAutoConfiguration&lt;/strong&gt; — Tells Spring to automatically configure components based on dependencies&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;@ComponentScan&lt;/strong&gt; — Tells Spring to scan this package and sub-packages for components&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Real-world analogy:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Think of &lt;code&gt;@SpringBootApplication&lt;/code&gt; like turning the key in your car ignition. One action triggers a cascade: fuel pumps activate, the starter motor turns, engine cylinders fire, and your car comes to life. Similarly, this one annotation triggers Spring to scan, configure, and bootstrap your entire application.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  4. Request &amp;amp; Response Models (DTOs)
&lt;/h2&gt;

&lt;p&gt;DTOs (Data Transfer Objects) are plain Java classes that carry data between the client and server. They're like order forms and invoices in a restaurant.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.1 TravelPlanRequest — The Order Form
&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;@Data&lt;/span&gt;
&lt;span class="nd"&gt;@Builder&lt;/span&gt;
&lt;span class="nd"&gt;@NoArgsConstructor&lt;/span&gt;
&lt;span class="nd"&gt;@AllArgsConstructor&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TravelPlanRequest&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@NotBlank&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="s"&gt;"Destination cannot be blank"&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;destination&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Min&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="mi"&gt;1&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="s"&gt;"Days must be between 1 and 30"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nd"&gt;@Max&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="mi"&gt;30&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="s"&gt;"Days must be between 1 and 30"&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;int&lt;/span&gt; &lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Positive&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="s"&gt;"Budget must be greater than 0"&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;budget&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;travelStyle&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@NotEmpty&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="s"&gt;"Interests cannot be empty"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;interests&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Annotation explanations with real-world examples:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;@NotBlank&lt;/code&gt;&lt;/strong&gt; — "Destination cannot be blank"&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Imagine booking a flight without specifying where you're going. The airline wouldn't know where to send you!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;@Min&lt;/code&gt; / &lt;code&gt;@Max&lt;/code&gt;&lt;/strong&gt; — Trip duration between 1 and 30 days&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A 0-day trip or a 100-day trip might not make practical sense. These constraints keep data realistic.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;@Positive&lt;/code&gt;&lt;/strong&gt; — Budget must be &amp;gt; 0&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can't travel with negative money (unfortunately!).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Lombok &lt;code&gt;@Data&lt;/code&gt;&lt;/strong&gt; generates getters, setters, &lt;code&gt;toString&lt;/code&gt;, &lt;code&gt;equals&lt;/code&gt;, and &lt;code&gt;hashCode&lt;/code&gt; automatically&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Without Lombok, you'd write ~50 lines of boilerplate code. With Lombok, just one annotation.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Real-world validation scenarios:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A travel booking website like MakeMyTrip uses similar validation: you can't search for a "0-day trip" or a "negative budget". These validations catch errors before they reach the database or AI service.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  4.2 TravelPlanResponse — The Invoice
&lt;/h3&gt;

&lt;p&gt;The response model is more complex because the AI returns a rich, structured travel plan. It contains nested classes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TravelPlanResponse&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;destination&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;tripOverview&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;DayItinerary&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;itinerary&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;EstimatedBudget&lt;/span&gt; &lt;span class="n"&gt;estimatedBudget&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;recommendedPlaces&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;food&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;transportation&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;packingChecklist&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;travelTips&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;safetyTips&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Nested classes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;DayItinerary&lt;/code&gt;&lt;/strong&gt; — Contains day number, title, activities, meals, and accommodation for each day&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Meals&lt;/code&gt;&lt;/strong&gt; — Breakfast, lunch, and dinner suggestions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;EstimatedBudget&lt;/code&gt;&lt;/strong&gt; — Total budget with category breakdown (accommodation, food, transport, etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Real-world example of the response:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When you ask an AI travel agent for a "5-day Goa trip," the response includes specific beach names (Baga, Anjuna, Vagator), exact restaurants, scooter rental prices (₹300-500/day), and even packing items like "waterproof phone pouch" — all generated dynamically by AI.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  5. Prompt Engineering with Spring AI
&lt;/h2&gt;

&lt;p&gt;Prompt engineering is the art of crafting instructions for AI models. This is where the magic happens.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.1 The Prompt Template File
&lt;/h3&gt;

&lt;p&gt;In &lt;code&gt;src/main/resources/prompts/travel-plan-prompt.st&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Generate a detailed travel plan for {destination} for {days} days with a budget of ₹{budget}.

Travel Style: {travelStyle}
Interests: {interests}

Provide the response as a JSON object with the following structure:
{
  "tripOverview": "...",
  "itinerary": [...],
  "estimatedBudget": {...},
  ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;{variable}&lt;/code&gt; syntax is from Spring AI's &lt;code&gt;PromptTemplate&lt;/code&gt;. Variables get replaced at runtime with actual values.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this approach is powerful:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Instead of hardcoding prompts in Java code, you keep them in separate &lt;code&gt;.st&lt;/code&gt; files. This means non-developers (like content writers or domain experts) can tweak prompts without touching code — a real productivity win in production teams.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  5.2 PromptTemplateConfig — Wiring the Template
&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;@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;PromptTemplateConfig&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;Resource&lt;/span&gt; &lt;span class="nf"&gt;travelPlanPromptResource&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;ClassPathResource&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"prompts/travel-plan-prompt.st"&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 creates a Spring bean that loads the prompt template file from the classpath. The &lt;code&gt;@Bean&lt;/code&gt; annotation makes this &lt;code&gt;Resource&lt;/code&gt; available for dependency injection elsewhere.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real-world analogy:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Think of this like registering a template document in your company's document management system. Anyone who needs the template can request it from the system instead of searching through folders.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  5.3 TravelPromptBuilder — Building Prompts
&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;@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;TravelPromptBuilder&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;SYSTEM_PROMPT_TEXT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""
            You are an experienced travel planner. Generate accurate, realistic,
            beginner-friendly travel itineraries with practical recommendations.
            Always provide structured responses, estimated budgets, local travel
            advice, and safety tips. Never invent nonexistent tourist attractions.
            """&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;Resource&lt;/span&gt; &lt;span class="n"&gt;promptTemplateResource&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;Prompt&lt;/span&gt; &lt;span class="nf"&gt;buildTravelPlanPrompt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TravelPlanRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;SystemMessage&lt;/span&gt; &lt;span class="n"&gt;systemMessage&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;SystemMessage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;SYSTEM_PROMPT_TEXT&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;PromptTemplate&lt;/span&gt; &lt;span class="n"&gt;promptTemplate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PromptTemplate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;promptTemplateResource&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;variables&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"destination"&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;getDestination&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
            &lt;span class="s"&gt;"days"&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="na"&gt;valueOf&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;getDays&lt;/span&gt;&lt;span class="o"&gt;()),&lt;/span&gt;
            &lt;span class="s"&gt;"budget"&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="na"&gt;valueOf&lt;/span&gt;&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="kt"&gt;int&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;getBudget&lt;/span&gt;&lt;span class="o"&gt;()),&lt;/span&gt;
            &lt;span class="s"&gt;"travelStyle"&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;getTravelStyle&lt;/span&gt;&lt;span class="o"&gt;()&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;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getTravelStyle&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Standard"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"interests"&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="na"&gt;join&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="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getInterests&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
        &lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="nc"&gt;Message&lt;/span&gt; &lt;span class="n"&gt;userMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;promptTemplate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createMessage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;variables&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;Prompt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;systemMessage&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;userMessage&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Breaking down the prompt structure:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Real-World Analogy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;System Message&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Sets the AI's role and behavior&lt;/td&gt;
&lt;td&gt;Like briefing a travel agent before they talk to a client: "You specialize in budget travel, always recommend local food, never make up attractions"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;User Message&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The actual request with variables&lt;/td&gt;
&lt;td&gt;Like the client saying: "I want to go to Goa for 5 days with ₹25,000, I love beaches and food"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PromptTemplate&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Fills variables into a template&lt;/td&gt;
&lt;td&gt;Like a mail-merge — same letter structure, personalized details&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Real-world scenario:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A travel tech startup could A/B test different system prompts to see which generates more bookings. By keeping prompts in files, non-engineers can run experiments without deployment cycles.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  6. The Service Layer — Core Business Logic
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;TravelPlanService&lt;/code&gt; is the brain of the application. It orchestrates the entire flow.&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;TravelPlanService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ChatClient&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;TravelPromptBuilder&lt;/span&gt; &lt;span class="n"&gt;promptBuilder&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;ObjectMapper&lt;/span&gt; &lt;span class="n"&gt;objectMapper&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;TravelPlanService&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="nc"&gt;TravelPromptBuilder&lt;/span&gt; &lt;span class="n"&gt;promptBuilder&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                              &lt;span class="nc"&gt;ObjectMapper&lt;/span&gt; &lt;span class="n"&gt;objectMapper&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;defaultSystem&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"You are an experienced travel planner..."&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="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;promptBuilder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;promptBuilder&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;objectMapper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;objectMapper&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;
  
  
  Constructor Injection
&lt;/h3&gt;

&lt;p&gt;Spring automatically provides the &lt;code&gt;ChatClient.Builder&lt;/code&gt; (configured via &lt;code&gt;application.yml&lt;/code&gt;), our &lt;code&gt;TravelPromptBuilder&lt;/code&gt;, and Jackson's &lt;code&gt;ObjectMapper&lt;/code&gt;. This is &lt;strong&gt;dependency injection&lt;/strong&gt; — Spring wires everything together.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real-world analogy:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Imagine a restaurant where the chef doesn't grow their own vegetables or make their own plates. The ingredients arrive from suppliers (Spring) already prepped. The chef just focuses on cooking.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The Generate Method — Step by Step
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;TravelPlanResponse&lt;/span&gt; &lt;span class="nf"&gt;generateTravelPlan&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TravelPlanRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Step 1: Build the prompt from user request&lt;/span&gt;
    &lt;span class="nc"&gt;Prompt&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;promptBuilder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;buildTravelPlanPrompt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Step 2: Send to Azure OpenAI and get response&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="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;call&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="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;errorMsg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;extractErrorMessage&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="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;AiServiceException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to generate travel plan: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;errorMsg&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="c1"&gt;// Step 3: Validate response isn't empty&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;response&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;response&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="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;AiServiceException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AI returned an empty response"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Step 4: Parse JSON into Java object&lt;/span&gt;
    &lt;span class="nc"&gt;TravelPlanResponse&lt;/span&gt; &lt;span class="n"&gt;travelPlan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parseResponse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getDestination&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;travelPlan&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step-by-step with a real-world example:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;User sends:&lt;/strong&gt; &lt;code&gt;{destination: "Goa", days: 5, budget: 25000, travelStyle: "Budget", interests: ["Beaches", "Food", "Nightlife"]}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompt built:&lt;/strong&gt; System message + "Generate a detailed travel plan for Goa for 5 days with a budget of ₹25000..."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI responds:&lt;/strong&gt; A 2000-word JSON document with 5-day itinerary, budget breakdown, safety tips, etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JSON parsed:&lt;/strong&gt; The raw string is converted into a &lt;code&gt;TravelPlanResponse&lt;/code&gt; Java object&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Response sent:&lt;/strong&gt; The formatted travel plan is returned as HTTP JSON response&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The extractJson Method — Handling AI Output
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;extractJson&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;text&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;indexOf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;'{'&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lastIndexOf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="sc"&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;text&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;substring&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;end&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this is needed:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;AI models sometimes wrap JSON in markdown code blocks (&lt;br&gt;
&lt;br&gt;
&lt;code&gt;json ...&lt;/code&gt;&lt;br&gt;
&lt;br&gt;
) or add explanatory text before/after. This method strips everything except the JSON structure — like panning for gold and keeping only the nuggets.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Real-world example:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Without this method, the AI might return:&lt;/p&gt;


&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Here's your travel plan!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;br&gt;
json&lt;br&gt;
{"destination": "Goa", ...}&lt;/p&gt;


&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hope you enjoy!
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;The &lt;code&gt;extractJson&lt;/code&gt; method cleans this into valid JSON.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The extractErrorMessage Method — User-Friendly Errors
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;extractErrorMessage&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="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="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getMessage&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;if&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="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&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="na"&gt;contains&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"rate limit"&lt;/span&gt;&lt;span class="o"&gt;)&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="na"&gt;contains&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"RateLimit"&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;"Rate limit exceeded. Please try again later."&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;contains&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"timeout"&lt;/span&gt;&lt;span class="o"&gt;)&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="na"&gt;contains&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Timeout"&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;"Request timed out. Please try again."&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;contains&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"401"&lt;/span&gt;&lt;span class="o"&gt;)&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="na"&gt;contains&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"403"&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;"Authentication failed. Check API credentials."&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;contains&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"429"&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;"Too many requests. Please slow down."&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"AI service is currently unavailable. Please try again later."&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 translates cryptic Azure error messages into human-readable messages. Instead of exposing stack traces to users, they get clear, actionable feedback.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. The Controller Layer — REST API
&lt;/h2&gt;

&lt;p&gt;The controller is the &lt;strong&gt;front door&lt;/strong&gt; of your application — it receives HTTP requests and sends back responses.&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/v1"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@Tag&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Travel Plan"&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;"AI-powered travel plan generation endpoints"&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;TravelPlanController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;@RestController&lt;/code&gt;&lt;/strong&gt; — Combines &lt;code&gt;@Controller&lt;/code&gt; and &lt;code&gt;@ResponseBody&lt;/code&gt;; every method returns JSON automatically&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;@RequestMapping("/api/v1")&lt;/code&gt;&lt;/strong&gt; — All endpoints start with &lt;code&gt;/api/v1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;@Tag&lt;/code&gt;&lt;/strong&gt; — For Swagger documentation grouping&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Endpoint
&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;@PostMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/travel-plan"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;ResponseEntity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;TravelPlanResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;generateTravelPlan&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nd"&gt;@Valid&lt;/span&gt; &lt;span class="nd"&gt;@RequestBody&lt;/span&gt; &lt;span class="nc"&gt;TravelPlanRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nc"&gt;Instant&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;Instant&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;now&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;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;"Received travel plan request - destination: {}, days: {}, budget: {}"&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;getDestination&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;getDays&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;getBudget&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

    &lt;span class="nc"&gt;TravelPlanResponse&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;travelPlanService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;generateTravelPlan&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;Duration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;between&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="nc"&gt;Instant&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;now&lt;/span&gt;&lt;span class="o"&gt;()).&lt;/span&gt;&lt;span class="na"&gt;toMillis&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;"Travel plan request completed in {} ms for {}"&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;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getDestination&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;ok&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key annotations:&lt;/strong&gt;&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;th&gt;Real-World Analogy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@PostMapping&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Maps HTTP POST requests to this method&lt;/td&gt;
&lt;td&gt;Like a specific mailbox labeled "New Travel Plan Requests"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@Valid&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Triggers validation before method executes&lt;/td&gt;
&lt;td&gt;Like a security check before entering a building&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@RequestBody&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Maps HTTP request body to Java object&lt;/td&gt;
&lt;td&gt;Like an automated form-filler&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ResponseEntity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Wraps response with status code and headers&lt;/td&gt;
&lt;td&gt;Like sending a package with proper shipping label&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Real-world API call:&lt;/strong&gt;&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;POST http://localhost:8080/api/v1/travel-plan
Content-Type: application/json

{
  "destination": "Goa",
  "days": 5,
  "budget": 25000,
  "travelStyle": "Budget",
  "interests": ["Beaches", "Food", "Nightlife"]
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The controller also includes detailed OpenAPI annotations (&lt;code&gt;@Operation&lt;/code&gt;, &lt;code&gt;@ApiResponses&lt;/code&gt;, &lt;code&gt;@ExampleObject&lt;/code&gt;) that auto-generate beautiful Swagger documentation — making it easy for frontend developers to understand and test the API.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Exception Handling &amp;amp; Error Responses
&lt;/h2&gt;

&lt;p&gt;This project uses Spring's &lt;code&gt;@RestControllerAdvice&lt;/code&gt; for centralized exception handling — one place to handle all errors.&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ProblemDetail — Modern Error Responses
&lt;/h3&gt;

&lt;p&gt;Spring Boot 3 introduced &lt;code&gt;ProblemDetail&lt;/code&gt; (RFC 9457) for standardized error responses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Validation errors (400 Bad Request):&lt;/strong&gt;&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"urn:problem-type:validation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Validation Failed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"detail"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"destination: Destination cannot be blank"&lt;/span&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-07-21T12:00:00Z"&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;&lt;strong&gt;AI service errors (503 Service Unavailable):&lt;/strong&gt;&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"urn:problem-type:ai-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;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AI Service Error"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;503&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"detail"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Failed to generate travel plan: Rate limit exceeded..."&lt;/span&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-07-21T12:00:00Z"&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;&lt;strong&gt;Why centralized error handling matters:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Imagine 10 different controllers each handling errors differently. Some return 400, some return "bad request", some return cryptic stack traces. &lt;code&gt;@RestControllerAdvice&lt;/code&gt; ensures every error response follows the same format — critical for frontend developers building error-handling logic.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Real-world analogy:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Think of this like a mall's customer service desk. Instead of each store handling complaints differently, there's one central desk with standard procedures. Customers know exactly where to go and what format to expect.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  9. OpenAPI / Swagger Configuration
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;OpenApiConfig&lt;/code&gt; class customizes the auto-generated API documentation:&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;OpenApiConfig&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;OpenAPI&lt;/span&gt; &lt;span class="nf"&gt;travelPlannerOpenAPI&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;OpenAPI&lt;/span&gt;&lt;span class="o"&gt;()&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Info&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AI Travel Planner API"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"REST API for generating personalized AI-powered travel plans"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"1.0.0"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;contact&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;Contact&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Developer"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"dev@example.com"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;license&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;License&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"MIT"&lt;/span&gt;&lt;span class="o"&gt;)))&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;servers&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Server&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://localhost:"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;serverPort&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Local development server"&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;&lt;strong&gt;What this provides:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;/swagger-ui.html&lt;/code&gt; page where you can test all endpoints interactively&lt;/li&gt;
&lt;li&gt;Auto-generated documentation based on your controller annotations&lt;/li&gt;
&lt;li&gt;Example requests and responses (from &lt;code&gt;@ExampleObject&lt;/code&gt; annotations in the controller)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Real-world use:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In a team of 5 developers, the frontend mobile team can open Swagger UI, see the exact request format expected, test with real data, and download the OpenAPI spec to auto-generate their API client code — all without waiting for the backend team to write documentation.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  10. Application Configuration
&lt;/h2&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;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;azure&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;openai&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;api-key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${AZURE_OPENAI_API_KEY}&lt;/span&gt;
        &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${AZURE_OPENAI_ENDPOINT}&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;deployment-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${AZURE_OPENAI_DEPLOYMENT:gpt-4o}&lt;/span&gt;
            &lt;span class="na"&gt;temperature&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key settings explained:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Why It Matters&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;api-key&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Your Azure OpenAI API key&lt;/td&gt;
&lt;td&gt;Without this, Azure won't authenticate your requests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;endpoint&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Your Azure OpenAI service URL&lt;/td&gt;
&lt;td&gt;Tells Spring which Azure resource to call&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;deployment-name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Which model deployment to use&lt;/td&gt;
&lt;td&gt;Different models (GPT-4o, GPT-4, GPT-35-turbo) have different capabilities and costs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;temperature: 1.0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Controls AI creativity (0.0-1.0)&lt;/td&gt;
&lt;td&gt;Lower = more deterministic; Higher = more creative. Some models only support specific values&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Real-world scenario:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In production, you'd never hardcode these values. Using &lt;code&gt;${AZURE_OPENAI_API_KEY}&lt;/code&gt; reads from environment variables, which can be set in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your local terminal for development&lt;/li&gt;
&lt;li&gt;GitHub Actions secrets for CI/CD&lt;/li&gt;
&lt;li&gt;Azure Key Vault for production&lt;/li&gt;
&lt;li&gt;Docker Compose environment variables&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  10.2 Dev Profile (&lt;code&gt;application-dev.yml&lt;/code&gt;)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;logging&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;com.aytravelplanner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DEBUG&lt;/span&gt;
    &lt;span class="na"&gt;org.springframework.ai&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DEBUG&lt;/span&gt;
    &lt;span class="na"&gt;org.springframework.web&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DEBUG&lt;/span&gt;

&lt;span class="na"&gt;spring&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;devtools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;DEBUG logging&lt;/strong&gt; shows detailed logs about AI prompts and responses — invaluable during development&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DevTools restart&lt;/strong&gt; automatically restarts the app when code changes — like hot-reload for Spring Boot&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  11. Running the Application
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Set Environment Variables
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Windows PowerShell&lt;/span&gt;
&lt;span class="nv"&gt;$env&lt;/span&gt;:AZURE_OPENAI_API_KEY&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-key-here"&lt;/span&gt;
&lt;span class="nv"&gt;$env&lt;/span&gt;:AZURE_OPENAI_ENDPOINT&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://your-resource.openai.azure.com"&lt;/span&gt;
&lt;span class="nv"&gt;$env&lt;/span&gt;:AZURE_OPENAI_DEPLOYMENT&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"gpt-4o"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; Keep your API key secret! Never commit it to Git. The &lt;code&gt;.env.example&lt;/code&gt; file shows what variables are needed without exposing real values.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Run the Application
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mvn spring-boot:run &lt;span class="nt"&gt;-Dspring-boot&lt;/span&gt;.run.profiles&lt;span class="o"&gt;=&lt;/span&gt;dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;dev&lt;/code&gt; profile enables detailed logging and hot-reload.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Verify It's Running
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Swagger UI:&lt;/strong&gt; &lt;a href="http://localhost:8080/swagger-ui.html" rel="noopener noreferrer"&gt;http://localhost:8080/swagger-ui.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API Docs:&lt;/strong&gt; &lt;a href="http://localhost:8080/v3/api-docs" rel="noopener noreferrer"&gt;http://localhost:8080/v3/api-docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Health Check:&lt;/strong&gt; &lt;a href="http://localhost:8080/actuator/health" rel="noopener noreferrer"&gt;http://localhost:8080/actuator/health&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  12. Testing the API
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Using cURL
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; &lt;span class="s1"&gt;'POST'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s1"&gt;'http://localhost:8080/api/v1/travel-plan'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'accept: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
  "destination": "Goa",
  "days": 5,
  "budget": 25000,
  "travelStyle": "Budget",
  "interests": ["Beaches", "Food", "Nightlife"]
}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Using Swagger UI
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;a href="http://localhost:8080/swagger-ui.html" rel="noopener noreferrer"&gt;http://localhost:8080/swagger-ui.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Click on the "Travel Plan" endpoint&lt;/li&gt;
&lt;li&gt;Click "Try it out"&lt;/li&gt;
&lt;li&gt;Paste the JSON request body&lt;/li&gt;
&lt;li&gt;Click "Execute" — the response appears below with the full travel plan&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Sample Response Snippet
&lt;/h3&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;"destination"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Goa"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tripOverview"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"5-day budget beach-and-food-focused trip..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"itinerary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"day"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Arrival, Settle in &amp;amp; North Goa Beach Evening"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"activities"&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="s2"&gt;"Arrive in Goa. Check in to your budget guesthouse."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"Rent a scooter to explore nearby beaches."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"Relax at Calangute/Candolim beach..."&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"estimatedBudget"&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;"total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;25000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"breakdown"&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;"accommodation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"food"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"transportation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"activities"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"miscellaneous"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  13. Real-World Use Cases
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Use Case 1: Travel Booking Website
&lt;/h3&gt;

&lt;p&gt;A website like MakeMyTrip or Booking.com could integrate this API to offer "AI-Generated Itineraries" as a premium feature:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User searches for flights to Goa&lt;/li&gt;
&lt;li&gt;After booking, API generates a personalized 5-day itinerary&lt;/li&gt;
&lt;li&gt;User can modify and save it&lt;/li&gt;
&lt;li&gt;Monetization: charge ₹99 per AI-generated itinerary&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Use Case 2: Travel Agency Automation
&lt;/h3&gt;

&lt;p&gt;A small travel agency handling 100+ inquiries daily:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Customer calls: "I want a 7-day Kerala trip on ₹50,000"&lt;/li&gt;
&lt;li&gt;Agent inputs details into internal tool&lt;/li&gt;
&lt;li&gt;API generates professional itinerary in seconds instead of hours&lt;/li&gt;
&lt;li&gt;Agent reviews, customizes slightly, and sends to client&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Use Case 3: Chatbot Integration
&lt;/h3&gt;

&lt;p&gt;A WhatsApp travel chatbot:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User texts: "Plan a 3-day trip to Jaipur"&lt;/li&gt;
&lt;li&gt;Bot calls this API&lt;/li&gt;
&lt;li&gt;Returns formatted itinerary in chat&lt;/li&gt;
&lt;li&gt;User asks follow-ups: "Change budget to ₹30,000"&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Use Case 4: Content Generation
&lt;/h3&gt;

&lt;p&gt;A travel blog uses the API to generate content ideas:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Blog writer inputs: "Goa, 5 days, ₹25,000, Budget"&lt;/li&gt;
&lt;li&gt;API generates structured content&lt;/li&gt;
&lt;li&gt;Writer uses it as a starting template&lt;/li&gt;
&lt;li&gt;Customizes with personal experiences and photos&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  14. Troubleshooting Common Issues
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Error&lt;/th&gt;
&lt;th&gt;Cause&lt;/th&gt;
&lt;th&gt;Solution&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;No static resource swagger-ui.html&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Wrong Swagger path config&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;springdoc.swagger-ui.path: /swagger-ui.html&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DeploymentNotFound&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Wrong model deployment name&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;AZURE_OPENAI_DEPLOYMENT&lt;/code&gt; to match your Azure deployment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;temperature does not support 0.7&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Some models only support temperature=1&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;temperature: 1.0&lt;/code&gt; in configuration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;401 Authentication failed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Invalid API key&lt;/td&gt;
&lt;td&gt;Check &lt;code&gt;AZURE_OPENAI_API_KEY&lt;/code&gt; is correct and not expired&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;400 Bad Request&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Missing required fields&lt;/td&gt;
&lt;td&gt;Ensure &lt;code&gt;destination&lt;/code&gt;, &lt;code&gt;days&lt;/code&gt;, &lt;code&gt;budget&lt;/code&gt;, and &lt;code&gt;interests&lt;/code&gt; are provided&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI returns invalid JSON&lt;/td&gt;
&lt;td&gt;Model hallucination&lt;/td&gt;
&lt;td&gt;The &lt;code&gt;extractJson&lt;/code&gt; method handles this; improve the prompt as needed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Architecture Diagram
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────┐     HTTP Request      ┌──────────────────┐
│   Client    │ ───────────────────&amp;gt;   │   Controller     │
│ (cURL/App)  │                       │  (REST API)      │
└─────────────┘                       └────────┬─────────┘
                                               │
                                               ▼
                                     ┌──────────────────┐
                                     │    Service       │
                                     │  TravelPlanService│
                                     └────────┬─────────┘
                                               │
                                    ┌──────────┴──────────┐
                                    ▼                     ▼
                           ┌─────────────────┐   ┌─────────────────┐
                           │  PromptBuilder   │   │  ObjectMapper   │
                           │ (Build prompt)   │   │ (Parse JSON)    │
                           └────────┬────────┘   └────────┬────────┘
                                    │                     │
                                    ▼                     │
                           ┌─────────────────┐            │
                           │  ChatClient     │────────────┘
                           │ (Azure OpenAI)  │
                           └─────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






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

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Spring AI simplifies LLM integration&lt;/strong&gt; — The &lt;code&gt;ChatClient&lt;/code&gt; API abstracts away HTTP calls, authentication, and streaming&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompt engineering is crucial&lt;/strong&gt; — The quality of output depends entirely on how you structure your prompts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Always validate and parse AI output&lt;/strong&gt; — AI models don't guarantee valid JSON; always add safety nets&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Centralized error handling&lt;/strong&gt; — &lt;code&gt;@RestControllerAdvice&lt;/code&gt; keeps your error responses consistent&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Externalize configuration&lt;/strong&gt; — Use environment variables for secrets and profile-specific configs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Document APIs automatically&lt;/strong&gt; — SpringDoc + OpenAPI annotations generate interactive documentation for free&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Next Steps &amp;amp; Enhancements
&lt;/h2&gt;

&lt;p&gt;Want to extend this project? Try:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Add caching&lt;/strong&gt; — Store generated plans in Redis or database to avoid redundant AI calls&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add PDF export&lt;/strong&gt; — Convert the itinerary to a downloadable PDF using iText or Apache POI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add user authentication&lt;/strong&gt; — Spring Security + JWT for multi-user support&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add streaming responses&lt;/strong&gt; — Use SSE (Server-Sent Events) to show the itinerary as it's being generated&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Support multiple AI providers&lt;/strong&gt; — Add OpenAI, Anthropic Claude, or Google Gemini via Spring AI's abstraction&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add flight/hotel API integration&lt;/strong&gt; — Fetch real prices from Amadeus or Google Flights APIs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate limiting&lt;/strong&gt; — Protect your API from abuse with bucket4j or Spring Cloud Gateway&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Built with Spring Boot 3.5, Spring AI 1.0.8, Java 21, and Azure OpenAI.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>openai</category>
      <category>springboot</category>
      <category>api</category>
    </item>
    <item>
      <title>Understanding Hibernate ddl-auto in Spring Boot: When to Use create, update, validate, and none</title>
      <dc:creator>Ayush Shrivastava</dc:creator>
      <pubDate>Sun, 31 May 2026 21:03:53 +0000</pubDate>
      <link>https://dev.to/ayshriv/understanding-hibernate-ddl-auto-in-spring-boot-when-to-use-create-update-validate-and-none-5c8n</link>
      <guid>https://dev.to/ayshriv/understanding-hibernate-ddl-auto-in-spring-boot-when-to-use-create-update-validate-and-none-5c8n</guid>
      <description>&lt;p&gt;Choosing the wrong spring.jpa.hibernate.ddl-auto setting can lead to unexpected schema changes or even data loss. In this guide, you'll learn what each ddl-auto value does (none, validate, update, create, and create-drop), when to use them, real-world examples, common mistakes, and production-ready best practices using Flyway and Liquibase.&lt;/p&gt;

&lt;p&gt;Managing database schema changes is one of the most important responsibilities in any Spring Boot application. Whether you are building a small side project, a SaaS product, or a large enterprise system, understanding how Hibernate interacts with your database is critical.&lt;/p&gt;

&lt;p&gt;One configuration property that every Java developer should know is:&lt;/p&gt;

&lt;p&gt;spring.jpa.hibernate.ddl-auto&lt;/p&gt;

&lt;p&gt;This property controls how Hibernate manages the database schema during application startup.&lt;/p&gt;

&lt;p&gt;A wrong configuration can accidentally delete production data, while the correct configuration can improve developer productivity and deployment reliability.&lt;/p&gt;

&lt;p&gt;In this guide, we will explore every available value, when to use it, when to avoid it, and real-world examples from production systems.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is spring.jpa.hibernate.ddl-auto?
&lt;/h2&gt;

&lt;p&gt;Hibernate is an ORM (Object Relational Mapping) framework that maps Java entities to database tables.&lt;/p&gt;

&lt;p&gt;When the application starts, Hibernate compares your entity classes with the existing database schema.&lt;/p&gt;

&lt;p&gt;The ddl-auto property tells Hibernate what action to perform based on that comparison.&lt;/p&gt;

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

&lt;p&gt;spring.jpa.hibernate.ddl-auto=update&lt;/p&gt;

&lt;p&gt;Depending on the value, Hibernate can:&lt;/p&gt;

&lt;p&gt;• Create tables&lt;br&gt;
• Update tables&lt;br&gt;
• Validate tables&lt;br&gt;
• Drop tables&lt;br&gt;
• Ignore schema management completely&lt;/p&gt;




&lt;h2&gt;
  
  
  Available Values
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;none&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;spring.jpa.hibernate.ddl-auto=none&lt;/p&gt;

&lt;p&gt;Description:&lt;/p&gt;

&lt;p&gt;Hibernate does not perform any schema management operations.&lt;/p&gt;

&lt;p&gt;What Happens?&lt;/p&gt;

&lt;p&gt;• No table creation&lt;br&gt;
• No table updates&lt;br&gt;
• No schema validation&lt;br&gt;
• No schema modification&lt;/p&gt;

&lt;p&gt;Best For:&lt;/p&gt;

&lt;p&gt;• Production systems&lt;br&gt;
• Enterprise applications&lt;br&gt;
• Flyway migrations&lt;br&gt;
• Liquibase migrations&lt;/p&gt;

&lt;p&gt;Advantages:&lt;/p&gt;

&lt;p&gt;• Safe&lt;br&gt;
• Predictable&lt;br&gt;
• No accidental schema changes&lt;/p&gt;

&lt;p&gt;Disadvantages:&lt;/p&gt;

&lt;p&gt;• Developers must manage schema manually&lt;/p&gt;

&lt;p&gt;Real-World Example:&lt;/p&gt;

&lt;p&gt;A banking application manages schema changes using Flyway migration scripts.&lt;/p&gt;

&lt;p&gt;Hibernate should not modify the schema.&lt;/p&gt;

&lt;p&gt;spring.jpa.hibernate.ddl-auto=none&lt;/p&gt;




&lt;h2&gt;
  
  
  2. validate
&lt;/h2&gt;

&lt;p&gt;spring.jpa.hibernate.ddl-auto=validate&lt;/p&gt;

&lt;p&gt;Description:&lt;/p&gt;

&lt;p&gt;Hibernate validates that database tables match entity definitions.&lt;/p&gt;

&lt;p&gt;What Happens?&lt;/p&gt;

&lt;p&gt;• Checks table existence&lt;br&gt;
• Checks column existence&lt;br&gt;
• Checks data types&lt;br&gt;
• Throws exception if mismatch exists&lt;/p&gt;

&lt;p&gt;Best For:&lt;/p&gt;

&lt;p&gt;• Production&lt;br&gt;
• Staging&lt;br&gt;
• CI/CD validation&lt;/p&gt;

&lt;p&gt;Advantages:&lt;/p&gt;

&lt;p&gt;• Detects schema issues early&lt;br&gt;
• Prevents runtime failures&lt;/p&gt;

&lt;p&gt;Disadvantages:&lt;/p&gt;

&lt;p&gt;• Does not create or modify tables&lt;/p&gt;

&lt;p&gt;Real-World Example:&lt;/p&gt;

&lt;p&gt;Entity:&lt;/p&gt;

&lt;p&gt;&lt;a class="mentioned-user" href="https://dev.to/entity"&gt;@entity&lt;/a&gt;&lt;br&gt;
public class User {&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Id
private Long id;

private String name;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;If the database does not contain the "name" column, the application startup will fail immediately.&lt;/p&gt;

&lt;p&gt;This prevents deployment of incompatible code.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. update
&lt;/h2&gt;

&lt;p&gt;spring.jpa.hibernate.ddl-auto=update&lt;/p&gt;

&lt;p&gt;Description:&lt;/p&gt;

&lt;p&gt;Hibernate updates the schema based on entity changes.&lt;/p&gt;

&lt;p&gt;What Happens?&lt;/p&gt;

&lt;p&gt;• Creates missing tables&lt;br&gt;
• Creates missing columns&lt;br&gt;
• Updates schema where possible&lt;br&gt;
• Keeps existing data&lt;/p&gt;

&lt;p&gt;Best For:&lt;/p&gt;

&lt;p&gt;• Local development&lt;br&gt;
• Learning projects&lt;br&gt;
• Internal tools&lt;/p&gt;

&lt;p&gt;Advantages:&lt;/p&gt;

&lt;p&gt;• Fast development&lt;br&gt;
• Less manual database work&lt;/p&gt;

&lt;p&gt;Disadvantages:&lt;/p&gt;

&lt;p&gt;• Can create unexpected schema changes&lt;br&gt;
• Not suitable for production&lt;br&gt;
• Cannot handle complex migrations properly&lt;/p&gt;

&lt;p&gt;Real-World Example:&lt;/p&gt;

&lt;p&gt;Before:&lt;/p&gt;

&lt;p&gt;&lt;a class="mentioned-user" href="https://dev.to/entity"&gt;@entity&lt;/a&gt;&lt;br&gt;
public class User {&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Id
private Long id;

private String name;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;After:&lt;/p&gt;

&lt;p&gt;&lt;a class="mentioned-user" href="https://dev.to/entity"&gt;@entity&lt;/a&gt;&lt;br&gt;
public class User {&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Id
private Long id;

private String name;

private String phoneNumber;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;Hibernate automatically adds:&lt;/p&gt;

&lt;p&gt;ALTER TABLE users&lt;br&gt;
ADD phone_number VARCHAR(255);&lt;/p&gt;

&lt;p&gt;without deleting existing data.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. create
&lt;/h2&gt;

&lt;p&gt;spring.jpa.hibernate.ddl-auto=create&lt;/p&gt;

&lt;p&gt;Description:&lt;/p&gt;

&lt;p&gt;Hibernate drops all existing tables and recreates them.&lt;/p&gt;

&lt;p&gt;What Happens?&lt;/p&gt;

&lt;p&gt;• Existing tables removed&lt;br&gt;
• New tables created&lt;br&gt;
• All data lost&lt;/p&gt;

&lt;p&gt;Best For:&lt;/p&gt;

&lt;p&gt;• Learning&lt;br&gt;
• Demo projects&lt;br&gt;
• Quick prototypes&lt;/p&gt;

&lt;p&gt;Advantages:&lt;/p&gt;

&lt;p&gt;• Fresh schema every startup&lt;br&gt;
• Useful during rapid experimentation&lt;/p&gt;

&lt;p&gt;Disadvantages:&lt;/p&gt;

&lt;p&gt;• Deletes all existing data&lt;/p&gt;

&lt;p&gt;Real-World Example:&lt;/p&gt;

&lt;p&gt;While learning JPA relationships and entity mappings, developers often use:&lt;/p&gt;

&lt;p&gt;spring.jpa.hibernate.ddl-auto=create&lt;/p&gt;

&lt;p&gt;to rebuild the schema automatically.&lt;/p&gt;

&lt;p&gt;WARNING:&lt;/p&gt;

&lt;p&gt;Never use create in production.&lt;/p&gt;

&lt;p&gt;A single application restart can wipe out all business data.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. create-drop
&lt;/h2&gt;

&lt;p&gt;spring.jpa.hibernate.ddl-auto=create-drop&lt;/p&gt;

&lt;p&gt;Description:&lt;/p&gt;

&lt;p&gt;Creates schema during startup and removes schema during shutdown.&lt;/p&gt;

&lt;p&gt;What Happens?&lt;/p&gt;

&lt;p&gt;• Tables created on startup&lt;br&gt;
• Tables dropped on shutdown&lt;/p&gt;

&lt;p&gt;Best For:&lt;/p&gt;

&lt;p&gt;• Automated testing&lt;br&gt;
• Integration tests&lt;br&gt;
• Temporary environments&lt;/p&gt;

&lt;p&gt;Advantages:&lt;/p&gt;

&lt;p&gt;• Clean environment every run&lt;br&gt;
• Perfect for testing&lt;/p&gt;

&lt;p&gt;Disadvantages:&lt;/p&gt;

&lt;p&gt;• Data does not persist&lt;/p&gt;

&lt;p&gt;Real-World Example:&lt;/p&gt;

&lt;p&gt;JUnit integration tests often use:&lt;/p&gt;

&lt;p&gt;spring.jpa.hibernate.ddl-auto=create-drop&lt;/p&gt;

&lt;p&gt;Every test starts with a fresh database.&lt;/p&gt;




&lt;h2&gt;
  
  
  Comparison Table
&lt;/h2&gt;

&lt;p&gt;Value         Creates   Updates   Validates   Drops Data&lt;/p&gt;

&lt;p&gt;none          No        No        No          No&lt;br&gt;
validate      No        No        Yes         No&lt;br&gt;
update        Yes       Yes       No          No&lt;br&gt;
create        Yes       Yes       No          Yes&lt;br&gt;
create-drop   Yes       Yes       No          Yes&lt;/p&gt;




&lt;h2&gt;
  
  
  Recommended Usage
&lt;/h2&gt;

&lt;p&gt;Environment                Recommended Value&lt;/p&gt;

&lt;p&gt;Local Development          update&lt;br&gt;
Learning Projects          create&lt;br&gt;
Unit Testing               create-drop&lt;br&gt;
Integration Testing        create-drop&lt;br&gt;
QA/Staging                 validate&lt;br&gt;
Production                 validate&lt;br&gt;
Production + Flyway        none&lt;br&gt;
Production + Liquibase     none&lt;/p&gt;




&lt;h2&gt;
  
  
  Real-World Production Strategy
&lt;/h2&gt;

&lt;p&gt;Most modern applications use Flyway or Liquibase instead of allowing Hibernate to manage schemas automatically.&lt;/p&gt;

&lt;p&gt;Recommended Configuration:&lt;/p&gt;

&lt;p&gt;spring.jpa.hibernate.ddl-auto=validate&lt;/p&gt;

&lt;p&gt;or&lt;/p&gt;

&lt;p&gt;spring.jpa.hibernate.ddl-auto=none&lt;/p&gt;

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

&lt;p&gt;spring.jpa.hibernate.ddl-auto=validate&lt;br&gt;
spring.flyway.enabled=true&lt;/p&gt;

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

&lt;p&gt;-- V1__create_users.sql&lt;/p&gt;

&lt;p&gt;CREATE TABLE users (&lt;br&gt;
    id BIGINT PRIMARY KEY,&lt;br&gt;
    name VARCHAR(255)&lt;br&gt;
);&lt;/p&gt;

&lt;p&gt;Benefits:&lt;/p&gt;

&lt;p&gt;• Version controlled database changes&lt;br&gt;
• Better team collaboration&lt;br&gt;
• Easier rollbacks&lt;br&gt;
• Predictable deployments&lt;br&gt;
• Safer production releases&lt;/p&gt;




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

&lt;p&gt;Mistake #1&lt;/p&gt;

&lt;p&gt;Using update in production.&lt;/p&gt;

&lt;p&gt;Problem:&lt;/p&gt;

&lt;p&gt;Unexpected schema modifications can occur.&lt;/p&gt;

&lt;p&gt;Mistake #2&lt;/p&gt;

&lt;p&gt;Using create in production.&lt;/p&gt;

&lt;p&gt;Problem:&lt;/p&gt;

&lt;p&gt;Complete data loss after restart.&lt;/p&gt;

&lt;p&gt;Mistake #3&lt;/p&gt;

&lt;p&gt;Not validating schema before deployment.&lt;/p&gt;

&lt;p&gt;Problem:&lt;/p&gt;

&lt;p&gt;Application starts but fails later due to schema mismatch.&lt;/p&gt;

&lt;p&gt;Mistake #4&lt;/p&gt;

&lt;p&gt;Relying on Hibernate for database migrations.&lt;/p&gt;

&lt;p&gt;Problem:&lt;/p&gt;

&lt;p&gt;Complex migrations become difficult to manage.&lt;/p&gt;




&lt;h2&gt;
  
  
  Best Practices
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Use update only during development.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use create-drop for testing.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use validate in staging and production.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use Flyway or Liquibase for schema migrations.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Never use create in production.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Keep schema changes version controlled.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Review migration scripts before deployment.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




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

&lt;p&gt;The spring.jpa.hibernate.ddl-auto property plays a critical role in how Hibernate manages your database schema.&lt;/p&gt;

&lt;p&gt;Choosing the correct value depends on your environment and deployment strategy.&lt;/p&gt;

&lt;p&gt;Quick Summary:&lt;/p&gt;

&lt;p&gt;• none → No schema management.&lt;br&gt;
• validate → Only validates schema.&lt;br&gt;
• update → Updates schema automatically.&lt;br&gt;
• create → Recreates schema every startup.&lt;br&gt;
• create-drop → Creates schema on startup and removes it on shutdown.&lt;/p&gt;

&lt;p&gt;For most professional Spring Boot applications:&lt;/p&gt;

&lt;p&gt;Development → update&lt;br&gt;
Testing → create-drop&lt;br&gt;
Production → validate or none&lt;/p&gt;

&lt;p&gt;Combining Hibernate with Flyway or Liquibase provides the safest and most scalable approach for managing database changes in modern applications.&lt;/p&gt;

</description>
      <category>springboot</category>
      <category>hibernate</category>
      <category>jpa</category>
      <category>microservices</category>
    </item>
    <item>
      <title>Layered vs Feature-Based Package Structure in Spring Boot</title>
      <dc:creator>Ayush Shrivastava</dc:creator>
      <pubDate>Thu, 28 May 2026 21:10:15 +0000</pubDate>
      <link>https://dev.to/ayshriv/layered-vs-feature-based-package-structure-in-spring-boot-5659</link>
      <guid>https://dev.to/ayshriv/layered-vs-feature-based-package-structure-in-spring-boot-5659</guid>
      <description>&lt;h1&gt;
  
  
  Traditional Layered vs Feature-Based Package Structure in Spring Boot
&lt;/h1&gt;

&lt;p&gt;As Java developers, one of the first things we decide when creating a new Spring Boot project is how to organize the codebase.&lt;/p&gt;

&lt;p&gt;Most of us start with a simple layered structure because that's what we see in tutorials, courses, and sample projects. It works well in the beginning, but as the project grows, managing the code can become difficult.&lt;/p&gt;

&lt;p&gt;Recently, while working on larger backend applications, I found myself moving away from the traditional layered approach and adopting a feature-based package structure instead.&lt;/p&gt;

&lt;p&gt;In this article, I'll compare both approaches and share where each one works best.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Traditional Layered Package Structure
&lt;/h2&gt;

&lt;p&gt;This is the structure most Spring Boot developers are familiar with.&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/java
│
├── controller
├── service
├── repository
├── entity
├── dto
├── config
├── security
└── exception
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Packages are organized by technical responsibility.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Controllers handle incoming requests.&lt;/li&gt;
&lt;li&gt;Services contain business logic.&lt;/li&gt;
&lt;li&gt;Repositories communicate with the database.&lt;/li&gt;
&lt;li&gt;Entities represent database tables.&lt;/li&gt;
&lt;li&gt;DTOs are used for API communication.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A typical request flow 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;Client
  │
  ▼
Controller
  │
  ▼
Service
  │
  ▼
Repository
  │
  ▼
Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At first glance, this structure looks clean and organized.&lt;/p&gt;

&lt;p&gt;And honestly, for many small applications, it works perfectly fine.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem as the Project Grows
&lt;/h2&gt;

&lt;p&gt;Let's say your application has the following modules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User Management&lt;/li&gt;
&lt;li&gt;Orders&lt;/li&gt;
&lt;li&gt;Payments&lt;/li&gt;
&lt;li&gt;Products&lt;/li&gt;
&lt;li&gt;Notifications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now imagine you need to modify the Order feature.&lt;/p&gt;

&lt;p&gt;You may end up jumping across multiple packages:&lt;br&gt;
&lt;/p&gt;

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

service/OrderService

repository/OrderRepository

entity/Order

dto/OrderRequest
dto/OrderResponse
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything related to Orders is scattered throughout the project.&lt;/p&gt;

&lt;p&gt;As more features are added, navigating the codebase becomes slower and feature ownership becomes less obvious.&lt;/p&gt;




&lt;h2&gt;
  
  
  Advantages of Layered Architecture
&lt;/h2&gt;

&lt;p&gt;There is a reason why this structure is still popular.&lt;/p&gt;

&lt;h3&gt;
  
  
  Easy to Learn
&lt;/h3&gt;

&lt;p&gt;New developers can understand it quickly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Clear Separation of Responsibilities
&lt;/h3&gt;

&lt;p&gt;Each layer has a specific purpose.&lt;/p&gt;

&lt;h3&gt;
  
  
  Great for Small Applications
&lt;/h3&gt;

&lt;p&gt;For CRUD applications and small services, it keeps things simple.&lt;/p&gt;

&lt;h3&gt;
  
  
  Common Across Many Projects
&lt;/h3&gt;

&lt;p&gt;Most Spring Boot tutorials and open-source projects follow this pattern.&lt;/p&gt;




&lt;h2&gt;
  
  
  Disadvantages of Layered Architecture
&lt;/h2&gt;

&lt;p&gt;As the codebase grows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Feature-related code becomes scattered.&lt;/li&gt;
&lt;li&gt;Navigation becomes harder.&lt;/li&gt;
&lt;li&gt;Merge conflicts increase.&lt;/li&gt;
&lt;li&gt;Team ownership becomes less clear.&lt;/li&gt;
&lt;li&gt;Refactoring requires touching multiple packages.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The structure is organized around technology, not around business functionality.&lt;/p&gt;




&lt;h1&gt;
  
  
  Moving to Feature-Based Package Structure
&lt;/h1&gt;

&lt;p&gt;Instead of organizing code by technical layers, we can organize it by business features.&lt;/p&gt;

&lt;p&gt;A feature-based structure 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;src/main/java
│
├── user
│   ├── controller
│   ├── service
│   ├── repository
│   ├── dto
│   └── entity
│
├── order
│   ├── controller
│   ├── service
│   ├── repository
│   ├── dto
│   └── entity
│
├── payment
│   ├── controller
│   ├── service
│   ├── repository
│   ├── dto
│   └── entity
│
├── shared
├── config
└── security
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now every feature owns its complete implementation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Example
&lt;/h2&gt;

&lt;p&gt;Everything related to Orders stays together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;order
│
├── OrderController
├── OrderService
├── OrderRepository
├── OrderEntity
├── OrderDto
└── OrderMapper
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a developer needs to work on the Order module, they already know where everything is located.&lt;/p&gt;

&lt;p&gt;No more jumping between five different packages.&lt;/p&gt;




&lt;h2&gt;
  
  
  Advantages of Feature-Based Architecture
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Better Code Navigation
&lt;/h3&gt;

&lt;p&gt;All files related to a feature are grouped together.&lt;/p&gt;

&lt;h3&gt;
  
  
  Easier Maintenance
&lt;/h3&gt;

&lt;p&gt;Changes remain within a single domain.&lt;/p&gt;

&lt;h3&gt;
  
  
  Better Team Ownership
&lt;/h3&gt;

&lt;p&gt;Teams can own individual business domains.&lt;/p&gt;

&lt;h3&gt;
  
  
  Faster Development
&lt;/h3&gt;

&lt;p&gt;Developers spend less time searching for files.&lt;/p&gt;

&lt;h3&gt;
  
  
  Easier Scaling
&lt;/h3&gt;

&lt;p&gt;New features can be added without impacting existing modules.&lt;/p&gt;

&lt;h3&gt;
  
  
  Microservice Friendly
&lt;/h3&gt;

&lt;p&gt;Feature boundaries are already defined, making future extraction easier.&lt;/p&gt;




&lt;h2&gt;
  
  
  Potential Challenges
&lt;/h2&gt;

&lt;p&gt;Feature-based architecture is not perfect.&lt;/p&gt;

&lt;p&gt;A few things to keep in mind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Shared utilities must be managed carefully.&lt;/li&gt;
&lt;li&gt;Developers need consistent package naming conventions.&lt;/li&gt;
&lt;li&gt;Beginners may find it slightly unfamiliar at first.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, these challenges are usually small compared to the maintainability benefits gained in larger applications.&lt;/p&gt;




&lt;h2&gt;
  
  
  Which One Should You Choose?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Use Layered Architecture When:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Building small applications&lt;/li&gt;
&lt;li&gt;Learning Spring Boot&lt;/li&gt;
&lt;li&gt;Creating prototypes&lt;/li&gt;
&lt;li&gt;Working on simple CRUD systems&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Use Feature-Based Architecture When:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Building large applications&lt;/li&gt;
&lt;li&gt;Working with multiple developers&lt;/li&gt;
&lt;li&gt;Managing several business domains&lt;/li&gt;
&lt;li&gt;Following Domain-Driven Design principles&lt;/li&gt;
&lt;li&gt;Planning long-term project growth&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  My Recommendation
&lt;/h2&gt;

&lt;p&gt;For side projects and small services, I still use the traditional layered structure because it's simple and quick.&lt;/p&gt;

&lt;p&gt;But for production applications with multiple modules, I prefer feature-based packaging.&lt;/p&gt;

&lt;p&gt;When a project reaches dozens of APIs and multiple developers start contributing, keeping everything related to a business feature together makes a huge difference.&lt;/p&gt;

&lt;p&gt;The goal of project structure is not to create more folders.&lt;/p&gt;

&lt;p&gt;The goal is to make the code easier to understand, easier to maintain, and easier to scale.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;There is no universally correct package structure.&lt;/p&gt;

&lt;p&gt;The best architecture depends on your team's size, project complexity, and future requirements.&lt;/p&gt;

&lt;p&gt;The layered approach remains a great starting point, but for growing Spring Boot applications, feature-based packaging often provides a cleaner and more maintainable solution.&lt;/p&gt;

&lt;p&gt;How do you organize your Spring Boot projects?&lt;/p&gt;

&lt;p&gt;Do you still prefer the traditional layered approach, or have you moved to feature-based packaging?&lt;/p&gt;

&lt;p&gt;Let me know in the comments.&lt;/p&gt;

</description>
      <category>ayshriv</category>
      <category>springboot</category>
      <category>java</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
