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.
Now it is time to understand two of the most important concepts in Spring AI:
ChatModelChatClient
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.
The simple relationship is:
Your Java Code
|
v
ChatClient
|
v
ChatModel
|
v
AI Provider
The ChatClient provides the developer-friendly API, while the ChatModel represents the underlying AI model integration.
Let's understand this with a real example.
What Is ChatModel in Spring AI?
ChatModel is a lower-level abstraction for communicating with an AI model.
It provides the contract that Spring AI uses to interact with different AI providers.
For example:
OpenAI
Azure OpenAI
Google
Mistral
Spring AI can provide provider-specific implementations such as:
OpenAiChatModel
GeminiChatModel
MistralChatModel
The purpose of ChatModel is to hide provider-specific communication details behind a common abstraction.
Think about it like this:
ChatModel
|
+-- OpenAI
|
+-- Gemini
|
+-- Mistral
|
+-- Other providers
Your application can work with the abstraction instead of directly building HTTP requests for every provider.
What Is ChatClient?
ChatClient is a higher-level abstraction built on top of ChatModel.
It provides a fluent API that makes it easier to work with AI models.
For example:
String response = chatClient
.prompt("Explain Spring Boot dependency injection")
.call()
.content();
Instead of manually handling the lower-level model interaction, you work with a much simpler API.
The PDF describes ChatClient as handling prompt construction, chat history, model invocation, and extracting response content.
ChatModel vs ChatClient
Here is the easiest way to remember the difference:
| ChatModel | ChatClient |
|---|---|
| Lower-level abstraction | Higher-level abstraction |
| Represents AI model integration | Developer-friendly interface |
| Handles provider communication | Builds and manages prompts |
| More technical | Fluent and easier to use |
| Used underneath ChatClient | Uses ChatModel internally |
A simple analogy from the course material is:
ChatModel = Engine
ChatClient = Steering wheel + Dashboard
The engine performs the actual work.
The steering wheel and dashboard give you an easier way to control it.
How ChatClient and ChatModel Work Together
Suppose you write:
chatClient
.prompt("What is Spring Boot?")
.call()
.content();
What actually happens?
The flow is approximately:
Your Java Code
|
v
ChatClient
|
v
Prompt
|
v
ChatModel
|
v
AI Provider API
|
v
AI Response
|
v
ChatClient
|
v
String Response
Spring Boot auto-configuration creates the appropriate ChatModel based on your configured provider.
Spring AI then provides an auto-configured ChatClient.Builder that is wired to the model.
When you call the fluent ChatClient API, the request eventually delegates to the underlying ChatModel.
Let's Build a Real Example
Let's create a simple Spring Boot API:
GET /api/chat?message=Explain dependency injection
The API should send the message to an AI model and return the generated response.
Our architecture will be:
Client
|
| GET /api/chat
v
Spring Boot Controller
|
v
ChatClient
|
v
ChatModel
|
v
AI Provider
Step 1: Create the ChatController
Create:
@RestController
@RequestMapping("/api")
public class ChatController {
private final ChatClient chatClient;
public ChatController(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder.build();
}
@GetMapping("/chat")
public String chat(@RequestParam("message") String message) {
return chatClient
.prompt(message)
.call()
.content();
}
}
The important part is:
ChatClient.Builder
Spring Boot provides this builder through auto-configuration when the appropriate Spring AI model dependency is present.
Then we create our client:
this.chatClient = chatClientBuilder.build();
Now we can use it anywhere inside our service or controller.
Step 2: Send a Prompt
Let's send:
GET /api/chat?message=What is dependency injection in Spring?
The code:
chatClient
.prompt(message)
.call()
.content();
can be understood as:
prompt()
|
| Build the request
v
call()
|
| Execute the model call
v
content()
|
| Extract generated text
v
String
This is one of the biggest advantages of ChatClient.
The API is simple enough that a Java developer can understand what is happening without dealing directly with provider-specific request objects.
Why Not Use ChatModel Directly?
You might now ask:
If ChatModel communicates with the AI provider, why do we need ChatClient?
You can work at the ChatModel level when you need lower-level control.
But most application developers usually want to express something like:
chatClient
.prompt("Explain Java records")
.call()
.content();
rather than manually constructing model requests.
The ChatClient abstraction provides:
- Fluent APIs
- Prompt construction
- Message handling
- Model invocation
- Response extraction
- Synchronous and streaming programming models
These are some of the capabilities highlighted in the Spring AI course material.
A Spring Boot Developer's Mental Model
If you already know Spring Data, you can think about abstractions.
For example:
Spring Data
|
v
Repository Abstraction
|
v
Database Implementation
Similarly:
Spring AI
|
v
ChatClient
|
v
ChatModel
|
v
AI Provider
The goal is to avoid coupling your application code unnecessarily to the implementation details.
What Happens During Spring Boot Auto-Configuration?
One of the useful features of Spring AI is Spring Boot auto-configuration.
When you add the appropriate Spring AI dependency and configuration, Spring Boot can create the necessary model bean.
Conceptually:
Spring Boot Application Starts
|
v
Spring AI Configuration
|
v
Create ChatModel Bean
|
v
Create ChatClient.Builder
|
v
Your Controller
You don't have to manually create every object.
You simply inject:
ChatClient.Builder
and build the client:
this.chatClient = chatClientBuilder.build();
This follows the familiar Spring dependency injection model.
ChatClient Is More Than a Simple Wrapper
It might initially look like ChatClient is just a shortcut for calling the model.
But it becomes much more useful as your application grows.
For example, Spring AI allows you to configure things such as:
System instructions
Advisors
Tools
Options
Prompt templates
Chat memory
Streaming
The PDF later introduces these capabilities as part of the Spring AI journey.
This means your code can evolve from:
chatClient
.prompt(message)
.call()
.content();
to something more sophisticated:
chatClient
.prompt()
.system("You are a helpful support assistant.")
.user(message)
.call()
.content();
And eventually:
ChatClient
|
+-- System Instructions
|
+-- Memory
|
+-- Advisors
|
+-- Tools
|
+-- RAG
|
+-- Model
This is why learning ChatClient early is important.
ChatClient Builder vs ChatClient
There are two concepts you will frequently see:
ChatClient.Builder
and:
ChatClient
They are not the same thing.
The builder is used to configure and create a ChatClient.
For example:
public ChatController(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder.build();
}
After calling:
build()
you have:
ChatClient
You can then use:
chatClient.prompt(...)
Think about it as:
ChatClient.Builder
|
| build()
v
ChatClient
|
| prompt()
v
ChatModel
Using ChatClient With a Local Model
One of the advantages of Spring AI is that you are not restricted to a hosted AI provider.
The PDF introduces Ollama as a way to run LLMs locally and demonstrates configuring Spring AI to use it.
For example, you can run:
ollama run llama3.2:1b
Then configure:
spring.ai.model.chat=ollama
spring.ai.ollama.chat.model=llama3.2:1b
With the Ollama starter:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-ollama</artifactId>
</dependency>
Your controller can remain almost identical:
@RestController
@RequestMapping("/api")
public class ChatController {
private final ChatClient chatClient;
public ChatController(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
@GetMapping("/chat")
public String chat(@RequestParam String message) {
return chatClient
.prompt(message)
.call()
.content();
}
}
This is a powerful concept.
Your application code can remain focused on the AI interaction rather than provider-specific implementation details.
What If We Need Multiple Models?
This is where things become more interesting.
Imagine you have a production application with different requirements.
You might want:
Simple Questions
|
v
Fast / Cheap Model
Complex Reasoning
|
v
Powerful Model
Local Development
|
v
Ollama
Why would you want multiple models?
The PDF identifies several real-world reasons:
Task-Based Model Selection
Use different models for different workloads.
For example:
Simple FAQ
-> Lightweight model
Complex analysis
-> More powerful model
Fallback Strategy
If one model is unavailable:
Primary Model
|
X
|
v
Fallback Model
A/B Testing
You can compare models based on:
Accuracy
Latency
Cost
User experience
User Preferences
Some applications may allow users to select a model.
Specialized Models
You could use one model for coding and another for creative content.
These scenarios are explicitly discussed in the PDF.
Multiple ChatClients
Spring AI's default configuration provides a single ChatClient.Builder.
That is enough for simple applications.
But if your application needs multiple models, you may need to configure multiple clients yourself.
The course material demonstrates disabling the default ChatClient builder auto-configuration:
spring.ai.chat.client.enabled=false
and creating multiple ChatClient instances manually.
For example:
@Configuration
public class ChatClientConfig {
@Bean
public ChatClient openAiChatClient(OpenAiChatModel chatModel) {
return ChatClient.create(chatModel);
}
@Bean
public ChatClient ollamaChatClient(OllamaChatModel chatModel) {
return ChatClient
.builder(chatModel)
.build();
}
}
Now you can have:
OpenAI ChatClient
|
v
OpenAI Model
Ollama ChatClient
|
v
Ollama Model
This gives your application more flexibility.
A Real-World Architecture
Imagine you're building an AI-powered customer support platform.
You could have:
Customer
|
v
Spring Boot API
|
v
ChatClient
|
+---------+---------+
| |
v v
Fast Model Powerful Model
| |
v v
Simple FAQs Complex Requests
Later, we might add:
ChatClient
|
+-------------+-------------+
| | |
v v v
Memory RAG Tools
| | |
v v v
History Company Docs Backend APIs
This is the direction in which a simple Spring AI application grows into a real AI backend.
ChatModel or ChatClient: Which One Should You Use?
For most application-level Spring AI development:
Prefer ChatClient
because it provides the higher-level API and integrates naturally with prompts, advisors, memory, tools, and other application concerns.
You should understand ChatModel because it is the underlying abstraction that connects your application to the actual model provider.
The mental model is:
ChatClient
|
| High-level developer API
v
ChatModel
|
| Provider integration
v
AI Model
Common Mistakes
Mistake 1: Thinking ChatClient Is the AI Model
It is not.
ChatClient != LLM
ChatClient is the interface your application uses to interact with the model.
Mistake 2: Thinking ChatModel and ChatClient Are Competing Alternatives
They serve different abstraction levels.
ChatClient
|
v
ChatModel
ChatClient uses ChatModel internally.
Mistake 3: Hard-Coding Provider Logic Everywhere
Avoid building your application around provider-specific HTTP calls when Spring AI already provides abstractions.
Keep your application logic separated from the AI provider whenever possible.
Mistake 4: Creating Multiple Clients Without a Reason
Multiple models can be useful, but they also introduce additional configuration and operational complexity.
Use them when you actually need:
Fallbacks
Different workloads
A/B testing
Cost optimization
Specialized models
Final Takeaway
The most important thing to remember from this article is:
ChatClient
|
| Developer-friendly API
v
ChatModel
|
| Model/provider integration
v
AI Provider
ChatModel is the lower-level abstraction responsible for communicating with AI providers.
ChatClient provides the higher-level fluent API that makes AI interactions easier to build and maintain.
For a typical Spring Boot application, you will spend much of your application-level development working with:
ChatClient
while Spring AI handles the underlying model integration.
In the next article, we will take this knowledge and build a complete Spring AI application with OpenAI, starting from project setup and configuration to creating a production-style REST endpoint.
Frequently Asked Questions
What is ChatModel in Spring AI?
ChatModel is the lower-level abstraction used to communicate with an AI model provider.
What is ChatClient?
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.
Is ChatClient better than ChatModel?
They serve different purposes. ChatClient is generally more convenient for application development, while ChatModel provides the lower-level model abstraction.
Can ChatClient work with Ollama?
Yes. The course material demonstrates using ChatClient with an Ollama-backed model.
Can Spring AI use multiple AI models?
Yes. Spring AI can be configured with multiple ChatClient instances for different models and use cases.
Spring AI Series
Part 1: Spring AI Tutorial: How Java Developers Can Build Generative AI Applications with Spring Boot
Part 2: ChatModel vs ChatClient in Spring AI
Part 3: Build Your First Spring AI Application with OpenAI
Part 4: Run Local LLMs with Ollama and Spring AI
Part 5: Run AI Models Locally with Docker Model Runner
Part 6: Using AWS Bedrock with Spring AI
Part 7: Working with Multiple Chat Models in Spring AI
Part 8: Understanding Message Roles in LLMs
Part 9: System Messages and User Messages in Spring AI
Part 10: Configuring Default Behavior in ChatClient
More articles will cover prompt templates, advisors, structured output, tokens, embeddings, chat memory, RAG, vector stores, tool calling, MCP, evaluation, observability, and AI agents.
Top comments (0)