DEV Community

Cover image for ChatModel vs ChatClient in Spring AI: Understanding the Core Abstractions
Ayush Shrivastava
Ayush Shrivastava

Posted on

ChatModel vs ChatClient in Spring AI: Understanding the Core Abstractions

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:

  • ChatModel
  • ChatClient

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Spring AI can provide provider-specific implementations such as:

OpenAiChatModel
GeminiChatModel
MistralChatModel
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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();
    }
}
Enter fullscreen mode Exit fullscreen mode

The important part is:

ChatClient.Builder
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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?
Enter fullscreen mode Exit fullscreen mode

The code:

chatClient
        .prompt(message)
        .call()
        .content();
Enter fullscreen mode Exit fullscreen mode

can be understood as:

prompt()
    |
    | Build the request
    v
call()
    |
    | Execute the model call
    v
content()
    |
    | Extract generated text
    v
String
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Similarly:

Spring AI
    |
    v
ChatClient
    |
    v
ChatModel
    |
    v
AI Provider
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

You don't have to manually create every object.

You simply inject:

ChatClient.Builder
Enter fullscreen mode Exit fullscreen mode

and build the client:

this.chatClient = chatClientBuilder.build();
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

to something more sophisticated:

chatClient
        .prompt()
        .system("You are a helpful support assistant.")
        .user(message)
        .call()
        .content();
Enter fullscreen mode Exit fullscreen mode

And eventually:

ChatClient
   |
   +-- System Instructions
   |
   +-- Memory
   |
   +-- Advisors
   |
   +-- Tools
   |
   +-- RAG
   |
   +-- Model
Enter fullscreen mode Exit fullscreen mode

This is why learning ChatClient early is important.


ChatClient Builder vs ChatClient

There are two concepts you will frequently see:

ChatClient.Builder
Enter fullscreen mode Exit fullscreen mode

and:

ChatClient
Enter fullscreen mode Exit fullscreen mode

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();
}
Enter fullscreen mode Exit fullscreen mode

After calling:

build()
Enter fullscreen mode Exit fullscreen mode

you have:

ChatClient
Enter fullscreen mode Exit fullscreen mode

You can then use:

chatClient.prompt(...)
Enter fullscreen mode Exit fullscreen mode

Think about it as:

ChatClient.Builder
        |
        | build()
        v
   ChatClient
        |
        | prompt()
        v
   ChatModel
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Then configure:

spring.ai.model.chat=ollama
spring.ai.ollama.chat.model=llama3.2:1b
Enter fullscreen mode Exit fullscreen mode

With the Ollama starter:

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-ollama</artifactId>
</dependency>
Enter fullscreen mode Exit fullscreen mode

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();
    }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Fallback Strategy

If one model is unavailable:

Primary Model
      |
      X
      |
      v
Fallback Model
Enter fullscreen mode Exit fullscreen mode

A/B Testing

You can compare models based on:

Accuracy
Latency
Cost
User experience
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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();
    }
}
Enter fullscreen mode Exit fullscreen mode

Now you can have:

OpenAI ChatClient
       |
       v
OpenAI Model


Ollama ChatClient
       |
       v
Ollama Model
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Later, we might add:

                 ChatClient
                     |
       +-------------+-------------+
       |             |             |
       v             v             v
    Memory          RAG          Tools
       |             |             |
       v             v             v
   History       Company Docs   Backend APIs
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Common Mistakes

Mistake 1: Thinking ChatClient Is the AI Model

It is not.

ChatClient != LLM
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Final Takeaway

The most important thing to remember from this article is:

ChatClient
    |
    | Developer-friendly API
    v
ChatModel
    |
    | Model/provider integration
    v
AI Provider
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)