TechByteByByte

Section 1 — Spring AI Architecture

Understand the core abstractions, modules and architecture that make up Spring AI.

Begin with the problem

Why Spring AI exists

A Spring Boot application should not need completely different business code for every model provider. Spring AI supplies common Java interfaces and a pipeline around them.

Controller → ChatClient → advisors → ChatModel → provider

What you will learn

  • Locate Spring AI between your business code and a model provider.
  • Distinguish ChatClient, ChatModel, advisors, and auto-configuration.
  • Follow one request through the architecture.
  • Know where provider-specific behavior can still appear.

Current official reference: Spring AI documentation for this topic. The examples below primarily preserve the stated 1.1.x course target. Where Spring AI 2.0 differs, the text must treat that behavior as version-specific rather than universal.

Target version: Most examples in this course target Spring AI 1.1.x on Spring Boot 3.5.x. The official documentation currently lists both 1.1.8 and 2.0.1 as stable lines. Spring AI 2.0.0 went GA on June 12, 2026 with a narrower core scope (first-class vendor SDKs for OpenAI/Anthropic/Google only, JSpecify null-safety, Jackson 3, a hard dependency on Spring Boot 4.0) and real breaking changes — package renames for MCP annotations, MCP transport artifact relocations, and removal of the built-in tool-execution loop from ChatModel in favor of a dedicated ToolCallingAdvisor. This book teaches 1.1.x as the primary target and calls out 2.0 migration notes in blockquoted “2.0 note” boxes wherever the API shape actually changed.

Analogy: The Universal Power Adapter & JDBC Drivers Imagine traveling across Europe, Asia, and America with different devices:

  • The Problem: Every country has a different shape of wall socket (OpenAI uses a specific JSON format, Anthropic expects a Messages list format, Google Gemini expects a Vertex format). If you try to wire your laptop plug directly into the wall, you’ll spark a fire or get blocked (you’d have to write manual, coupled HTTP clients for each).
  • The Solution (The Adapter): You buy a single Universal Power Adapter (Spring AI’s ChatModel interface). It exposes a standardized, friendly Spring plug face to your laptop (the portable call(Prompt) interface). Behind the scenes, the adapter handles physical socket shape fitting, voltage conversions, and safety fuses (wrapping HTTP REST client calls and mapping responses).
  • You write code against the universal adapter, and switch countries (providers) by simply rotating the adapter’s pins (swapping properties in application.yml).

📊 Visual Chart: Spring AI Multi-Tier Architecture Stack

Here is how request handling, auto-configuration, and interface abstractions stack together:

graph TD
    UserCode["1. User Service Code<br>(Calls generic ChatClient / ChatModel interfaces)"] --> ClientLayer["2. Orchestration Layer<br>(ChatClient, Advisors interceptors, PromptTemplate)"]

    subgraph SpringFramework ["Spring AI Framework Core"]
        ClientLayer --> PortLayer["3. Portability Layer<br>(ChatModel, EmbeddingModel, VectorStore interfaces)"]
        AutoConfig["Spring Boot Autoconfiguration<br>(Binds application.yml, injects dependencies)"] -.-> PortLayer
    end

    PortLayer --> WrapperLayer["4. Provider Wrapper Clients<br>(OpenAiChatModel, AnthropicChatModel clients)"]

    subgraph RemoteEndpoints ["External Provider Nodes"]
        WrapperLayer -->|HTTP REST client| OpenAI["api.openai.com REST API"]
        WrapperLayer -->|HTTP REST client| Anthropic["api.anthropic.com REST API"]
        WrapperLayer -->|HTTP REST client| Ollama["localhost:11434 Ollama Daemon"]
    end

1.1 What Is Spring AI, Really

Forget the marketing line (“Spring Boot for AI”). Structurally, Spring AI is three things stacked on top of each other:

  1. A portability layer — a set of vendor-agnostic interfaces (ChatModel, EmbeddingModel, ImageModel, VectorStore) that normalize wildly different provider SDKs (OpenAI’s REST shape, Anthropic’s Messages API, Ollama’s local HTTP API, Bedrock’s runtime client) into one Spring-idiomatic contract.
  2. An orchestration layerChatClient, the Advisor chain, PromptTemplate, and the tool-calling machinery, which give you a fluent, interceptable pipeline for turning a user request into a fully-formed model call and back.
  3. A Spring Boot integration layer — auto-configuration classes, @ConfigurationProperties, starter POMs, and ObservationRegistry wiring that make all of the above show up as beans with zero XML and full Actuator/Micrometer support.

The critical mental model: Spring AI does not run models. It never touches model weights, tokenizers, or inference (see the glossary if “weights” and “inference” are new terms — briefly: those are the actual machine-learning internals of an LLM, which live on the provider’s servers, not in your JVM). Every ChatModel implementation is a thin, well-typed HTTP client wrapper — OpenAI’s is backed by RestClient/WebClient calls to api.openai.com; Ollama’s calls your local daemon over HTTP. Spring AI’s job is request/response shaping, cross-cutting concerns (retry, observability, tool execution), and dependency wiring — the same job Spring Data does for databases, and Spring Security does for auth providers.

1.1.1 Why this framing matters

If you think of Spring AI as “a client library,” you’ll fight it. If you think of it as “Spring Data, but the datasource is a language model,” the design decisions stop looking arbitrary:

Spring Data patternSpring AI equivalent
JpaRepository<T, ID> abstracts SQL dialectsChatModel abstracts provider wire formats
@Query / QueryDSLPromptTemplate
EntityManager transaction boundaryChatClient request/response pipeline
Spring Data auto-config picks a DataSource beanSpring AI auto-config picks a ChatModel bean based on starter on classpath
HibernateProperties / spring.datasource.*spring.ai.openai.*, spring.ai.anthropic.*

1.2 Design Goals (and the Trade-Offs They Force)

Spring AI’s design goals are stated in its own governance docs as: portability, composability, and idiomatic Spring integration. Each one costs you something — understanding the cost is what separates engineers who fight the framework from engineers who use it well.

  1. Portability — swap OpenAI for Anthropic by changing a starter dependency and a property, not code. Cost: the common ChatOptions interface can only expose what every provider supports. Provider-specific knobs (Anthropic’s “thinking budget” for extended reasoning, OpenAI’s logprobs) require dropping down to AnthropicChatOptions/OpenAiChatOptions — you lose portability the moment you use a provider-specific feature, which is most of the time in serious production usage.
  2. Composability — Advisors, ToolCallbacks, and ChatMemory all compose through the same ChatClient.Builder fluent chain rather than inheritance. Cost: Advisor ordering is invisible unless you explicitly set .order(). Two advisors mutating the same AdvisedRequest in the wrong order is one of the most common production bugs (see §1.9).
  3. Idiomatic Spring integration — everything is a bean, configured via @ConfigurationProperties, auto-wired, observable via Micrometer. Cost: auto-configuration magic. If you don’t understand ChatModelAutoConfiguration’s conditional logic, “why did my ChatModel bean not get created” becomes a 45-minute debugging session instead of a 30-second one.

1.3 Core Modules — The Actual JAR Graph

Spring AI is not one JAR. It’s a BOM (spring-ai-bom) governing roughly 40 modules. Understanding the module graph tells you exactly what’s on your classpath and why auto-configuration behaves the way it does.

spring-ai-bom

├── spring-ai-commons ───────────── shared types: Document, Media, MimeType,
│                                    TokenCountEstimator, ResourceUtils

├── spring-ai-model ──────────────── core abstractions:
│     ├── ChatModel, StreamingChatModel
│     ├── EmbeddingModel
│     ├── ImageModel
│     ├── ModerationModel
│     └── ChatOptions, Prompt, Message hierarchy

├── spring-ai-client-chat ────────── ChatClient, ChatClient.Builder,
│                                    Advisor SPI, DefaultChatClient

├── spring-ai-model-chat-memory ──── ChatMemory abstraction
│                                    (MessageWindowChatMemory, ChatMemoryRepository SPI)

├── spring-ai-vector-store ───────── VectorStore interface, SearchRequest,
│                                    similarity math helpers

├── spring-ai-rag ────────────────── QueryTransformer, DocumentRetriever,
│                                    QueryAugmenter, RetrievalAugmentationAdvisor

├── spring-ai-tool ───────────────── ToolCallback, ToolCallingManager,
│                                    @Tool annotation, JSON-schema generation

├── spring-ai-mcp ────────────────── McpToolCallbackProvider, MCP client/server glue

├── [Provider modules] ───────────── spring-ai-openai, spring-ai-anthropic,
│                                    spring-ai-vertex-ai-gemini, spring-ai-ollama,
│                                    spring-ai-bedrock-converse, spring-ai-mistral-ai,
│                                    spring-ai-azure-openai (removed as of 1.1.5+ —
│                                    folded into spring-ai-openai)

├── [Vector store modules] ───────── spring-ai-pgvector-store, spring-ai-redis-store,
│                                    spring-ai-pinecone-store, spring-ai-qdrant-store,
│                                    spring-ai-milvus-store, spring-ai-weaviate-store,
│                                    spring-ai-elasticsearch-store,
│                                    spring-ai-mongodb-atlas-store

├── [Document reader modules] ────── spring-ai-pdf-document-reader (uses Apache PDFBox),
│                                    spring-ai-tika-document-reader,
│                                    spring-ai-markdown-document-reader

└── [Spring Boot starters] ───────── spring-ai-starter-model-openai,
                                     spring-ai-starter-vector-store-pgvector,
                                     spring-ai-starter-mcp-client, ...
                                     (each starter = auto-config + provider module,
                                      following the exact spring-boot-starter-*
                                      pattern you already know)

Key architectural insight: notice spring-ai-model has zero dependency on any provider module. ChatModel is a pure interface living in a provider-agnostic JAR. This is what makes runtime provider swapping possible — your service layer depends only on spring-ai-model + spring-ai-client-chat types (ChatClient, ChatModel, Prompt), never on spring-ai-openai types directly, unless you deliberately reach for provider-specific ChatOptions.


1.4 Auto-Configuration — What Actually Happens at Startup

This is the part most engineers gloss over and then get bitten by in production. Let’s trace it precisely for the OpenAI starter.

1.4.1 The Conditional Chain

spring-ai-autoconfigure-model-openai ships OpenAiChatAutoConfiguration, gated by:

@AutoConfiguration
@ConditionalOnClass(OpenAiApi.class)
@EnableConfigurationProperties({
    OpenAiConnectionProperties.class,
    OpenAiChatProperties.class
})
@ConditionalOnProperty(
    prefix = "spring.ai.openai.chat",
    name = "enabled",
    havingValue = "true",
    matchIfMissing = true
)
public class OpenAiChatAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean   // <-- YOUR @Bean ChatModel wins if you define one
    public OpenAiChatModel openAiChatModel(
            OpenAiConnectionProperties connectionProperties,
            OpenAiChatProperties chatProperties,
            ObjectProvider<RestClient.Builder> restClientBuilderProvider,
            ObjectProvider<WebClient.Builder> webClientBuilderProvider,
            ToolCallingManager toolCallingManager,
            RetryTemplate retryTemplate,
            ResponseErrorHandler responseErrorHandler,
            ObjectProvider<ObservationRegistry> observationRegistry) {
        // constructs OpenAiApi, wires RestClient, applies retry template,
        // registers ChatModelObservationConvention
        ...
    }
}

Execution order at startup:

1. Spring Boot scans META-INF/spring/
   org.springframework.boot.autoconfigure.AutoConfiguration.imports
   inside spring-ai-autoconfigure-model-openai.jar


2. @ConditionalOnClass(OpenAiApi.class) evaluated
   → is spring-ai-openai.jar (which contains OpenAiApi) on the classpath?
   → NO  → auto-config class silently skipped, no bean, no error
   → YES → continue


3. @ConditionalOnProperty(...matchIfMissing = true) evaluated
   → spring.ai.openai.chat.enabled absent or "true" → continue
   → explicitly "false" → skipped


4. @EnableConfigurationProperties binds application.yml → typed properties objects
   spring.ai.openai.api-key        → OpenAiConnectionProperties.apiKey
   spring.ai.openai.chat.options.* → OpenAiChatProperties.options


5. @ConditionalOnMissingBean check on openAiChatModel()
   → does the ApplicationContext already contain a ChatModel-compatible bean?
   → YES (you defined your own @Bean ChatModel) → auto-config bean backs off
   → NO  → auto-config bean is created


6. Bean method executes: builds OpenAiApi (HTTP client), wraps with RetryTemplate,
   registers ObservationRegistry hooks, returns OpenAiChatModel


7. If spring-ai-starter-model-chat-client is present, ChatClientAutoConfiguration
   creates a ChatClient.Builder bean, injecting the ChatModel from step 6.

Why this matters in production: if you add both spring-ai-starter-model-openai and spring-ai-starter-model-anthropic to the classpath, you now have two candidate ChatModel beans, and Spring Boot will fail fast at startup with a NoUniqueBeanDefinitionException unless you either (a) qualify injection points with @Qualifier, or (b) explicitly define your own primary ChatModel bean and let both auto-configs back off. This is the single most common “why won’t my app start” issue when teams try a multi-provider setup — see §1.10.

1.4.2 @ConditionalOnMissingBean Is Your Escape Hatch

Every auto-configured bean in Spring AI backs off if you define your own — this is the same contract as every other Spring Boot starter you already know. Production teams almost always end up writing a custom ChatModel (or RestClient.Builder) bean to inject org-specific concerns: mTLS, a custom ClientHttpRequestInterceptor for auth-token refresh, or routing through an internal LLM gateway. Auto-configuration is a sane default, not a ceiling.


1.5 Internal Design — The Request Lifecycle End-to-End

chatClient.builder(chatModel)
        .defaultSystem("You are a helpful assistant")
        .defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory))
        .build()
        .prompt()
        .user("What's the weather in Bengaluru?")
        .tools(weatherTool)
        .call()
        .content();
┌────────────────────────────────────────────────────────────────────────┐
│ 1. ChatClient.ChatClientRequestSpec builds an AdvisedRequest             │
│    (userText, systemText, tools, advisors, chatOptions all captured)    │
└─────────────────────────────────┬─────────────────────────────────────┘

┌────────────────────────────────────────────────────────────────────────┐
│ 2. Advisor chain executes (AroundAdvisorChain)                          │
│    Each advisor wraps the next: MessageChatMemoryAdvisor.around()       │
│    injects prior turns from ChatMemoryRepository into the Prompt        │
│    BEFORE the call, and persists the new assistant turn AFTER           │
└─────────────────────────────────┬─────────────────────────────────────┘

┌────────────────────────────────────────────────────────────────────────┐
│ 3. AdvisedRequest → Prompt assembly                                     │
│    SystemMessage + memory Messages + UserMessage → List<Message>        │
│    ToolCallback definitions attached to ChatOptions.toolCallbacks       │
└─────────────────────────────────┬─────────────────────────────────────┘

┌────────────────────────────────────────────────────────────────────────┐
│ 4. ChatModel.call(Prompt) — the actual HTTP call to the provider        │
│    Request DTOs mapped to provider wire format, response mapped back    │
└─────────────────────────────────┬─────────────────────────────────────┘

┌────────────────────────────────────────────────────────────────────────┐
│ 5. If the response contains tool calls: ToolCallingManager executes     │
│    them, appends ToolResponseMessage(s), loops back to step 4           │
│    (this internal loop is what §1.9/§9 call "the tool-calling loop")    │
└─────────────────────────────────┬─────────────────────────────────────┘

┌────────────────────────────────────────────────────────────────────────┐
│ 6. Advisor chain unwinds (the "after" half of each advisor's wrapping)  │
│    MessageChatMemoryAdvisor persists the final AssistantMessage         │
└─────────────────────────────────┬─────────────────────────────────────┘

                       ChatResponse returned to caller
                       .content() extracts the text

Beginner note: if “advisor” is a new term, think of Spring AI’s Advisor chain the same way you’d think of a Spring MVC HandlerInterceptor or a Servlet Filter chain — each advisor gets a chance to inspect/modify the request before the model is called, and inspect/modify the response after. Memory injection, logging, and RAG retrieval are all implemented as advisors precisely because “wrap around a call and touch both sides” is exactly the interceptor pattern.


1.6 Core Types — Quick Reference

ClassPackageRole
ChatModelorg.springframework.ai.chat.modelProvider-agnostic single-call contract: ChatResponse call(Prompt)
StreamingChatModelsameFlux<ChatResponse> stream(Prompt)
ChatClientorg.springframework.ai.chat.clientFluent orchestration façade over ChatModel + Advisors
ChatClient.BuildersameAssembles defaults (system prompt, advisors, tools) — request/prototype-scoped; never share one built ChatClient across unrelated contexts without per-request .mutate()
Promptorg.springframework.ai.chat.promptImmutable List<Message> + ChatOptions
Message (interface)sameSystemMessage, UserMessage, AssistantMessage, ToolResponseMessage
ChatOptions / OpenAiChatOptions etc.org.springframework.ai.chat.prompt / provider packagesModel params: temperature, maxTokens, tools
Advisororg.springframework.ai.chat.client.advisor.apiSPI for intercepting request/response (CallAroundAdvisor, StreamAroundAdvisor)
ChatMemoryorg.springframework.ai.chat.memoryConversation state abstraction
ChatMemoryRepositorysameStorage SPI: InMemoryChatMemoryRepository, JdbcChatMemoryRepository, RedisChatMemoryRepository
ToolCallbackorg.springframework.ai.toolWraps a Java method as an LLM-invocable tool with JSON Schema
ToolCallingManagersameResolves and executes tool calls returned by the model
EmbeddingModelorg.springframework.ai.embeddingEmbeddingResponse embedCall(EmbeddingRequest)
VectorStoreorg.springframework.ai.vectorstoresimilaritySearch(SearchRequest), add(List<Document>)
Documentorg.springframework.ai.documentUnit of content + metadata for RAG pipelines
DocumentReader / TextSplitterorg.springframework.ai.documentETL pipeline stages for RAG ingestion
RetrievalAugmentationAdvisororg.springframework.ai.rag.advisorPre-built Advisor wiring retrieval into the ChatClient pipeline

1.7 Spring AI vs. LangChain vs. LangGraph vs. Semantic Kernel

You already know these frameworks conceptually (or at least by reputation), so this is a direct architectural comparison, not an introduction.

DimensionSpring AILangChainLangGraphSemantic Kernel
Core abstractionChatModel + Advisor pipeline (linear, interceptor pattern)Runnable + LCEL (linear, composable pipe)Explicit state graph (nodes + edges, cyclic)Kernel + Planners + Plugins
Orchestration modelImperative, linear per-call; loops are explicit code or tool-calling recursion, not a first-class graphLCEL chains are linear; loops bolted on via AgentExecutorNative cyclic graph — the actual differentiator; built for multi-step agent loops with statePlanner-driven; semi-declarative
Multi-agent supportNone natively — compose multiple ChatClients yourself, or bridge to LangGraph4jAgents exist but state handling is ad hocFirst-class — this is LangGraph’s reason to existAgent Framework (newer, separate from core SK)
State/memoryChatMemory SPI, pluggable repositories (JDBC, Redis, Cassandra)ConversationBufferMemory et al., considered legacy in newer LangChainGraph State object, checkpointer-based (Postgres/SQLite checkpointers)ChatHistory object
Type safetyStrong — Java, compile-time checked DTOs, @ConfigurationPropertiesWeak — Python, dynamic typing, runtime schema validationWeak — same Python runtime as LangChainStrong-ish — C#/Java bindings, weaker ecosystem than Spring AI in Java
DI / enterprise integrationNative Spring Boot — beans, @ConfigurationProperties, Actuator, MicrometerNone — you build your own DI/config layerNonePartial — DI works in .NET, weaker in Java/Python
ObservabilityMicrometer ObservationRegistry out of the box, OTel export via standard Spring mechanismsLangSmith (separate paid product) is the primary observability storySame LangSmith story, or manual OTelLimited, mostly manual
Where it’s strongestEnterprise Java shops already running Spring Boot in production, needing AI as one more capability inside an existing service meshRapid prototyping, Python-first teams, huge integration surface (hundreds of tool integrations)Complex multi-step agentic workflows with explicit control flow, human-in-the-loop checkpoints.NET shops, or teams wanting a planner-first “skills” abstraction
Where it’s weakestNo native graph/cyclic orchestration; you hand-roll multi-agent coordination or bolt on LangGraph4jNo compile-time safety; dependency sprawl; version churn is notoriousSteeper learning curve; overkill for a single-turn chatbotSmaller community/ecosystem, slower feature parity with LangChain in Python

The decision that actually matters in practice: if your orchestration logic is fundamentally a DAG or cycle with branching, retries, and human approval gates, Spring AI alone will feel like you’re reimplementing a state machine badly with if/while around ChatClient calls. That’s exactly the scenario Application 4 (a multi-agent enterprise platform, referenced later in this series) bridges by pairing Spring AI’s ChatModel/tool layer with an explicit graph orchestrator, because Spring AI intentionally does not ship one.


1.8 When to Use Spring AI — and When Not To

Use Spring AI when:

  • You’re already running Spring Boot microservices and need LLM calls to participate in the same transaction boundaries, DI container, observability stack, and deployment pipeline as everything else.
  • You need compile-time type safety on prompts/responses/tool signatures at enterprise scale, where a Python script’s dynamic typing becomes a liability across a large team.
  • Your orchestration is fundamentally request/response with retrieval and tool-calling, not a complex multi-agent state machine.
  • You need first-class Micrometer/OTel observability without adopting a separate SaaS product.

Do NOT reach for Spring AI (alone) when:

  • Your core requirement is a complex, cyclic, multi-agent workflow with explicit state checkpoints and human-in-the-loop resumption — you’ll want LangGraph (Python) or LangGraph4j paired with Spring AI’s model layer, not Spring AI’s Advisor chain trying to simulate a graph.
  • You’re prototyping against dozens of long-tail community integrations that only exist in the LangChain Python ecosystem (obscure document loaders, niche vector stores) — Spring AI’s provider matrix, while growing fast, is narrower.
  • Your team has zero Java/Spring investment and the AI feature is a standalone product, not a capability bolted onto an existing Spring estate — the DI/auto-configuration machinery is overhead you don’t need.

1.9 Common Mistakes (Section 1 Level)

  1. Injecting ChatClient as a shared singleton and mutating it per-requestChatClient instances built from .mutate() are cheap; the Builder is where defaults live. Don’t build one ChatClient at startup and try to bolt per-request system prompts onto it with shared mutable state.
  2. Assuming ChatOptions is fully portable — switching providers without auditing which provider-specific options you’re using (e.g., OpenAiChatOptions.builder().parallelToolCalls(true)) silently drops functionality on providers that don’t support it, or throws at runtime depending on the implementation.
  3. Multiple provider starters on the classpath with no @Primary or @Qualifier — causes NoUniqueBeanDefinitionException at boot, not at call time. Fails fast, but confuses engineers who expect Spring AI to “just pick one.”
  4. Not reading @ConditionalOnMissingBean before overriding auto-config — teams write a fully custom ChatModel bean, copy-pasting 80% of the auto-configured one, instead of injecting a custom RestClient.Builder/ClientHttpRequestInterceptor and letting auto-config assemble the rest.

1.10 Debugging Auto-Configuration Failures

Run this whenever a Spring AI bean isn’t showing up:

# 1. Turn on the auto-configuration report
java -jar app.jar --debug

# 2. Grep the report for your provider
#    Look for "Positive matches" vs "Negative matches"
#    under OpenAiChatAutoConfiguration
OpenAiChatAutoConfiguration matched:
   - @ConditionalOnClass found required class 'OpenAiApi'
   - @ConditionalOnProperty (spring.ai.openai.chat.enabled) matched
OpenAiChatAutoConfiguration#openAiChatModel:
   Did not match:
      - @ConditionalOnMissingBean (types: ChatModel; SearchStrategy: all)
        found beans of type 'org.springframework.ai.chat.model.ChatModel' myCustomChatModel

That single log block tells you exactly why: your own myCustomChatModel bean caused the auto-configured one to back off — which is often correct behavior, not a bug, but is invisible unless you go looking.


1.11 Interview Questions

  1. Why does Spring AI define ChatModel as a separate module (spring-ai-model) from provider implementations? What architectural property does this enable?
  2. Walk through what happens, in order, when both spring-ai-starter-model-openai and spring-ai-starter-model-anthropic are on the classpath with no explicit bean qualification.
  3. What is the actual mechanism by which ChatClient executes a multi-turn tool-calling loop internally, and how do you disable it?
  4. Contrast Spring AI’s Advisor chain with LangChain’s LCEL. Which supports cyclic execution natively, and why does that matter for agentic workflows?
  5. Why is ChatOptions portability described as a leaky abstraction? Give a concrete example of a provider-specific option that breaks portability.
  6. What does @ConditionalOnMissingBean buy you as an extension point, versus writing your own ChatModel from scratch?
  7. Where does the actual HTTP call happen inside OpenAiChatModel, and what class performs the DTO transformation from Spring AI’s Prompt to the provider’s wire format?
  8. Why doesn’t Spring AI ship a native multi-agent graph orchestrator, and what’s the recommended pattern to add one?
  9. What’s the risk of treating a ChatClient built via .build() as an application-scoped singleton mutated per-request?
  10. Explain the module boundary between spring-ai-commons and spring-ai-model — what lives in each and why the split exists.
  11. How does Spring AI’s ObservationRegistry integration differ from LangChain’s LangSmith-based observability model?
  12. What breaking changes does Spring AI 2.0 introduce relative to 1.1.x, and why would an enterprise team delay migration?
  13. Name the SPI interface that lets you swap ChatMemory storage from in-memory to JDBC to Redis without changing service code.
  14. What’s the difference between ChatModel.call() and StreamingChatModel.stream() at the interface level, and what does that imply for backpressure handling downstream?
  15. Why does the --debug auto-configuration report matter more in Spring AI diagnostics than typical Spring Boot web apps?
  16. In the request lifecycle diagram, at which exact step does memory get injected into the prompt, and at which step does it get persisted?
  17. What determines whether a ToolCallback executes synchronously inside ChatModel.call() versus being handed back to your code?
  18. Why is Document (the RAG unit) a separate abstraction from Message (a chat turn)? What would break if they were unified?
  19. Compare Spring AI’s DI-based configuration model to Semantic Kernel’s planner-based approach. What enterprise concern does DI solve that a planner doesn’t?
  20. If you needed mTLS and a rotating bearer token for an internal LLM gateway, which extension point would you use, and why is that preferable to forking the auto-configured ChatModel bean?

1.12 Best Practices Checklist

  • Depend only on spring-ai-model + spring-ai-client-chat types in service code; isolate provider-specific ChatOptions behind a config/adapter layer.
  • Never share one mutable ChatClient across concurrent requests with request-specific defaults baked in — use .mutate() per request or per-request Builder scoping.
  • Run --debug in staging at least once per provider integration to confirm the exact auto-configuration match/no-match reasoning.
  • Explicitly qualify ChatModel beans (@Qualifier, @Primary) the moment you add a second provider starter — don’t rely on classpath ordering.
  • Treat internalToolExecutionEnabled(false) as the default for any tool with side effects (writes, emails, payments) so you control the execution boundary explicitly rather than letting the loop run unattended.
  • Pin exact Spring AI + Spring Boot BOM versions in your parent POM; do not float + ranges given the CVE cadence visible in the 2026 release history (multiple CVEs across the 1.0.x/1.1.x/2.0.0-Mx streams — for example CVE-2026-47835, fixed in 1.1.8/1.0.9).

1.13 Key Takeaways

  • Spring AI is a portability + orchestration + Spring-integration layer, not an inference engine.
  • The module graph mirrors Spring Data’s provider-abstraction pattern — internalize that analogy and the auto-configuration behavior stops being mysterious.
  • The Advisor chain is Spring AI’s interceptor pipeline; the tool-calling loop is a flag on ChatOptions, not a separate agent framework.
  • Spring AI has no native cyclic multi-agent graph — that’s an intentional scope boundary, not an oversight, and one of the worked applications in this series shows the bridging pattern.
  • As of this writing (August 2026), both 1.1.x and 2.0.x are stable release lines; 2.0 has carries breaking changes most enterprise teams haven’t yet absorbed, and requires Spring Boot 4.0 as a hard dependency.

End of Section 1. Next: Section 2 — Prompt API (Prompt, PromptTemplate, structured/reusable prompts, internal rendering pipeline).

Author
TechByteByByte Editorial Team
Reviewed by
TechByteByByte Admin
Published
Last reviewed