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
ChatModelin favor of a dedicatedToolCallingAdvisor. 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
ChatModelinterface). It exposes a standardized, friendly Spring plug face to your laptop (the portablecall(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:
- 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. - An orchestration layer —
ChatClient, 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. - A Spring Boot integration layer — auto-configuration classes,
@ConfigurationProperties, starter POMs, andObservationRegistrywiring 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 pattern | Spring AI equivalent |
|---|---|
JpaRepository<T, ID> abstracts SQL dialects | ChatModel abstracts provider wire formats |
@Query / QueryDSL | PromptTemplate |
EntityManager transaction boundary | ChatClient request/response pipeline |
Spring Data auto-config picks a DataSource bean | Spring 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.
- Portability — swap OpenAI for Anthropic by changing a starter dependency and a
property, not code. Cost: the common
ChatOptionsinterface can only expose what every provider supports. Provider-specific knobs (Anthropic’s “thinking budget” for extended reasoning, OpenAI’slogprobs) require dropping down toAnthropicChatOptions/OpenAiChatOptions— you lose portability the moment you use a provider-specific feature, which is most of the time in serious production usage. - Composability — Advisors,
ToolCallbacks, andChatMemoryall compose through the sameChatClient.Builderfluent chain rather than inheritance. Cost: Advisor ordering is invisible unless you explicitly set.order(). Two advisors mutating the sameAdvisedRequestin the wrong order is one of the most common production bugs (see §1.9). - Idiomatic Spring integration — everything is a bean, configured via
@ConfigurationProperties, auto-wired, observable via Micrometer. Cost: auto-configuration magic. If you don’t understandChatModelAutoConfiguration’s conditional logic, “why did myChatModelbean 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
HandlerInterceptoror a ServletFilterchain — 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
| Class | Package | Role |
|---|---|---|
ChatModel | org.springframework.ai.chat.model | Provider-agnostic single-call contract: ChatResponse call(Prompt) |
StreamingChatModel | same | Flux<ChatResponse> stream(Prompt) |
ChatClient | org.springframework.ai.chat.client | Fluent orchestration façade over ChatModel + Advisors |
ChatClient.Builder | same | Assembles defaults (system prompt, advisors, tools) — request/prototype-scoped; never share one built ChatClient across unrelated contexts without per-request .mutate() |
Prompt | org.springframework.ai.chat.prompt | Immutable List<Message> + ChatOptions |
Message (interface) | same | SystemMessage, UserMessage, AssistantMessage, ToolResponseMessage |
ChatOptions / OpenAiChatOptions etc. | org.springframework.ai.chat.prompt / provider packages | Model params: temperature, maxTokens, tools |
Advisor | org.springframework.ai.chat.client.advisor.api | SPI for intercepting request/response (CallAroundAdvisor, StreamAroundAdvisor) |
ChatMemory | org.springframework.ai.chat.memory | Conversation state abstraction |
ChatMemoryRepository | same | Storage SPI: InMemoryChatMemoryRepository, JdbcChatMemoryRepository, RedisChatMemoryRepository |
ToolCallback | org.springframework.ai.tool | Wraps a Java method as an LLM-invocable tool with JSON Schema |
ToolCallingManager | same | Resolves and executes tool calls returned by the model |
EmbeddingModel | org.springframework.ai.embedding | EmbeddingResponse embedCall(EmbeddingRequest) |
VectorStore | org.springframework.ai.vectorstore | similaritySearch(SearchRequest), add(List<Document>) |
Document | org.springframework.ai.document | Unit of content + metadata for RAG pipelines |
DocumentReader / TextSplitter | org.springframework.ai.document | ETL pipeline stages for RAG ingestion |
RetrievalAugmentationAdvisor | org.springframework.ai.rag.advisor | Pre-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.
| Dimension | Spring AI | LangChain | LangGraph | Semantic Kernel |
|---|---|---|---|---|
| Core abstraction | ChatModel + Advisor pipeline (linear, interceptor pattern) | Runnable + LCEL (linear, composable pipe) | Explicit state graph (nodes + edges, cyclic) | Kernel + Planners + Plugins |
| Orchestration model | Imperative, linear per-call; loops are explicit code or tool-calling recursion, not a first-class graph | LCEL chains are linear; loops bolted on via AgentExecutor | Native cyclic graph — the actual differentiator; built for multi-step agent loops with state | Planner-driven; semi-declarative |
| Multi-agent support | None natively — compose multiple ChatClients yourself, or bridge to LangGraph4j | Agents exist but state handling is ad hoc | First-class — this is LangGraph’s reason to exist | Agent Framework (newer, separate from core SK) |
| State/memory | ChatMemory SPI, pluggable repositories (JDBC, Redis, Cassandra) | ConversationBufferMemory et al., considered legacy in newer LangChain | Graph State object, checkpointer-based (Postgres/SQLite checkpointers) | ChatHistory object |
| Type safety | Strong — Java, compile-time checked DTOs, @ConfigurationProperties | Weak — Python, dynamic typing, runtime schema validation | Weak — same Python runtime as LangChain | Strong-ish — C#/Java bindings, weaker ecosystem than Spring AI in Java |
| DI / enterprise integration | Native Spring Boot — beans, @ConfigurationProperties, Actuator, Micrometer | None — you build your own DI/config layer | None | Partial — DI works in .NET, weaker in Java/Python |
| Observability | Micrometer ObservationRegistry out of the box, OTel export via standard Spring mechanisms | LangSmith (separate paid product) is the primary observability story | Same LangSmith story, or manual OTel | Limited, mostly manual |
| Where it’s strongest | Enterprise Java shops already running Spring Boot in production, needing AI as one more capability inside an existing service mesh | Rapid 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 weakest | No native graph/cyclic orchestration; you hand-roll multi-agent coordination or bolt on LangGraph4j | No compile-time safety; dependency sprawl; version churn is notorious | Steeper learning curve; overkill for a single-turn chatbot | Smaller 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)
- Injecting
ChatClientas a shared singleton and mutating it per-request —ChatClientinstances built from.mutate()are cheap; theBuilderis where defaults live. Don’t build oneChatClientat startup and try to bolt per-request system prompts onto it with shared mutable state. - Assuming
ChatOptionsis 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. - Multiple provider starters on the classpath with no
@Primaryor@Qualifier— causesNoUniqueBeanDefinitionExceptionat boot, not at call time. Fails fast, but confuses engineers who expect Spring AI to “just pick one.” - Not reading
@ConditionalOnMissingBeanbefore overriding auto-config — teams write a fully customChatModelbean, copy-pasting 80% of the auto-configured one, instead of injecting a customRestClient.Builder/ClientHttpRequestInterceptorand 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
- Why does Spring AI define
ChatModelas a separate module (spring-ai-model) from provider implementations? What architectural property does this enable? - Walk through what happens, in order, when both
spring-ai-starter-model-openaiandspring-ai-starter-model-anthropicare on the classpath with no explicit bean qualification. - What is the actual mechanism by which
ChatClientexecutes a multi-turn tool-calling loop internally, and how do you disable it? - Contrast Spring AI’s Advisor chain with LangChain’s LCEL. Which supports cyclic execution natively, and why does that matter for agentic workflows?
- Why is
ChatOptionsportability described as a leaky abstraction? Give a concrete example of a provider-specific option that breaks portability. - What does
@ConditionalOnMissingBeanbuy you as an extension point, versus writing your ownChatModelfrom scratch? - Where does the actual HTTP call happen inside
OpenAiChatModel, and what class performs the DTO transformation from Spring AI’sPromptto the provider’s wire format? - Why doesn’t Spring AI ship a native multi-agent graph orchestrator, and what’s the recommended pattern to add one?
- What’s the risk of treating a
ChatClientbuilt via.build()as an application-scoped singleton mutated per-request? - Explain the module boundary between
spring-ai-commonsandspring-ai-model— what lives in each and why the split exists. - How does Spring AI’s
ObservationRegistryintegration differ from LangChain’s LangSmith-based observability model? - What breaking changes does Spring AI 2.0 introduce relative to 1.1.x, and why would an enterprise team delay migration?
- Name the SPI interface that lets you swap
ChatMemorystorage from in-memory to JDBC to Redis without changing service code. - What’s the difference between
ChatModel.call()andStreamingChatModel.stream()at the interface level, and what does that imply for backpressure handling downstream? - Why does the
--debugauto-configuration report matter more in Spring AI diagnostics than typical Spring Boot web apps? - In the request lifecycle diagram, at which exact step does memory get injected into the prompt, and at which step does it get persisted?
- What determines whether a
ToolCallbackexecutes synchronously insideChatModel.call()versus being handed back to your code? - Why is
Document(the RAG unit) a separate abstraction fromMessage(a chat turn)? What would break if they were unified? - 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?
- 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
ChatModelbean?
1.12 Best Practices Checklist
- Depend only on
spring-ai-model+spring-ai-client-chattypes in service code; isolate provider-specificChatOptionsbehind a config/adapter layer. - Never share one mutable
ChatClientacross concurrent requests with request-specific defaults baked in — use.mutate()per request or per-request Builder scoping. - Run
--debugin staging at least once per provider integration to confirm the exact auto-configuration match/no-match reasoning. - Explicitly qualify
ChatModelbeans (@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