Spring AI Chat Client provides a fluent API for building AI-powered applications with LLMs, supporting advisors, streaming, structured outputs, and conversation memory
Spring AI Chat Client provides built-in advisors for managing conversation history and memory. These advisors automatically store and retrieve messages from previous interactions, enabling multi-turn conversations with context awareness.
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.client.advisor.PromptChatMemoryAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.InMemoryChatMemory;
import org.springframework.ai.chat.prompt.PromptTemplate;
import reactor.core.scheduler.Scheduler;
import reactor.core.scheduler.Schedulers;Both memory advisors require a ChatMemory implementation to store conversation history.
Common Implementation:
import org.springframework.ai.chat.memory.InMemoryChatMemory;
ChatMemory chatMemory = new InMemoryChatMemory();For production use, implement ChatMemory with persistent storage (database, Redis, etc.).
Injects conversation history by adding previous messages directly to the prompt's message list.
class MessageChatMemoryAdvisor implements BaseChatMemoryAdvisor {
static Builder builder(ChatMemory chatMemory);
}interface Builder {
Builder conversationId(String conversationId);
Builder order(int order);
Builder scheduler(Scheduler scheduler);
MessageChatMemoryAdvisor build();
}Builder Methods:
conversationId() - Set conversation ID for grouping messages (default: "default")order() - Set advisor execution order (default: Advisor.DEFAULT_CHAT_MEMORY_PRECEDENCE_ORDER)scheduler() - Set reactive scheduler (default: Schedulers.boundedElastic())import org.springframework.ai.chat.memory.InMemoryChatMemory;
ChatMemory chatMemory = new InMemoryChatMemory();
ChatClient client = ChatClient.builder(chatModel)
.defaultAdvisors(
MessageChatMemoryAdvisor.builder(chatMemory).build()
)
.build();
// First message
String response1 = client
.prompt("My name is Alice")
.call()
.content();
// Second message - memory automatically includes previous exchange
String response2 = client
.prompt("What is my name?")
.call()
.content();
// Response will reference "Alice" using conversation historyUse conversation IDs to maintain separate conversations for different users or sessions.
MessageChatMemoryAdvisor memoryAdvisor =
MessageChatMemoryAdvisor.builder(chatMemory)
.conversationId("user-123")
.build();
ChatClient client = ChatClient.builder(chatModel)
.defaultAdvisors(memoryAdvisor)
.build();Override conversation ID for specific requests using advisor parameters.
ChatClient client = ChatClient.builder(chatModel)
.defaultAdvisors(
MessageChatMemoryAdvisor.builder(chatMemory).build()
)
.build();
// User 1's conversation
String response1 = client
.prompt()
.user("I like Java")
.advisors(spec -> spec.param("conversationId", "user-1"))
.call()
.content();
// User 2's conversation (separate history)
String response2 = client
.prompt()
.user("I like Python")
.advisors(spec -> spec.param("conversationId", "user-2"))
.call()
.content();
// User 1's follow-up (remembers Java)
String response3 = client
.prompt()
.user("What language do I like?")
.advisors(spec -> spec.param("conversationId", "user-1"))
.call()
.content();MessageChatMemoryAdvisor memoryAdvisor =
MessageChatMemoryAdvisor.builder(chatMemory)
.order(Advisor.DEFAULT_CHAT_MEMORY_PRECEDENCE_ORDER)
.build();Memory advisors work seamlessly with streaming responses.
ChatClient client = ChatClient.builder(chatModel)
.defaultAdvisors(
MessageChatMemoryAdvisor.builder(chatMemory)
.conversationId("stream-user")
.build()
)
.build();
Flux<String> stream = client
.prompt("Continue our story")
.stream()
.content();
stream.subscribe(System.out::print);Injects conversation history by inserting it into the system prompt using a template. This approach keeps message history as text rather than separate message objects.
class PromptChatMemoryAdvisor implements BaseChatMemoryAdvisor {
static Builder builder(ChatMemory chatMemory);
}interface Builder {
Builder systemPromptTemplate(PromptTemplate systemPromptTemplate);
Builder conversationId(String conversationId);
Builder order(int order);
Builder scheduler(Scheduler scheduler);
PromptChatMemoryAdvisor build();
}Builder Methods:
systemPromptTemplate() - Set template with {conversation_history} placeholderconversationId() - Set conversation ID (default: "default")order() - Set advisor execution order (default: Advisor.DEFAULT_CHAT_MEMORY_PRECEDENCE_ORDER)scheduler() - Set reactive scheduler (default: Schedulers.boundedElastic())PromptChatMemoryAdvisor memoryAdvisor =
PromptChatMemoryAdvisor.builder(chatMemory)
.systemPromptTemplate("""
You are a helpful assistant.
Conversation history:
{conversation_history}
""")
.build();
ChatClient client = ChatClient.builder(chatModel)
.defaultAdvisors(memoryAdvisor)
.build();
// Conversation with history injected into system prompt
String response = client
.prompt("What did we discuss earlier?")
.call()
.content();String template = """
You are a coding assistant.
Previous conversation:
{conversation_history}
Use the conversation history to provide context-aware responses.
""";
PromptChatMemoryAdvisor memoryAdvisor =
PromptChatMemoryAdvisor.builder(chatMemory)
.systemPromptTemplate(template)
.conversationId("code-assistant")
.build();ChatClient client = ChatClient.builder(chatModel)
.defaultAdvisors(
PromptChatMemoryAdvisor.builder(chatMemory)
.systemPromptTemplate("""
Context: {conversation_history}
""")
.build()
)
.build();
client
.prompt()
.user("Remember: I work at Acme Corp")
.advisors(spec -> spec.param("conversationId", "user-123"))
.call()
.content();
client
.prompt()
.user("Where do I work?")
.advisors(spec -> spec.param("conversationId", "user-123"))
.call()
.content();Both memory advisors implement this interface.
interface BaseChatMemoryAdvisor extends BaseAdvisor {
String getConversationId(
Map<String, Object> context,
String defaultConversationId
);
}Method:
getConversationId() - Extract conversation ID from context or use defaultThis allows conversation ID to be passed via advisor parameters at request time.
Use when:
Pros:
Cons:
Use when:
Pros:
Cons:
import org.springframework.ai.chat.memory.InMemoryChatMemory;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class ChatService {
private final ChatClient chatClient;
private final ChatMemory chatMemory = new InMemoryChatMemory();
public ChatService(ChatModel chatModel) {
this.chatClient = ChatClient.builder(chatModel)
.defaultSystem("You are a helpful assistant")
.defaultAdvisors(
MessageChatMemoryAdvisor.builder(chatMemory).build()
)
.build();
}
public String chat(String userId, String message) {
return chatClient
.prompt()
.user(message)
.advisors(spec -> spec.param("conversationId", userId))
.call()
.content();
}
public Flux<String> chatStream(String userId, String message) {
return chatClient
.prompt()
.user(message)
.advisors(spec -> spec.param("conversationId", userId))
.stream()
.content();
}
}String systemPrompt = """
You are a technical support assistant for our product.
Previous conversation with this customer:
{conversation_history}
Use the conversation history to:
- Remember previously discussed issues
- Avoid repeating questions
- Provide personalized assistance
""";
PromptChatMemoryAdvisor memoryAdvisor =
PromptChatMemoryAdvisor.builder(chatMemory)
.systemPromptTemplate(systemPrompt)
.build();
ChatClient supportBot = ChatClient.builder(chatModel)
.defaultAdvisors(memoryAdvisor)
.build();
// Customer interaction
String response1 = supportBot
.prompt()
.user("I'm having issues with login")
.advisors(spec -> spec.param("conversationId", "ticket-789"))
.call()
.content();
// Follow-up in same conversation
String response2 = supportBot
.prompt()
.user("The error is 'Invalid credentials'")
.advisors(spec -> spec.param("conversationId", "ticket-789"))
.call()
.content();import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
ChatClient client = ChatClient.builder(chatModel)
.defaultAdvisors(
// Memory advisor runs early to inject history
MessageChatMemoryAdvisor.builder(chatMemory)
.order(Advisor.DEFAULT_CHAT_MEMORY_PRECEDENCE_ORDER)
.build(),
// Logger runs last to see complete request
SimpleLoggerAdvisor.builder()
.order(Ordered.LOWEST_PRECEDENCE)
.build()
)
.build();public class ConversationSession {
private final String sessionId;
private final ChatClient chatClient;
public ConversationSession(
String sessionId,
ChatClient chatClient
) {
this.sessionId = sessionId;
this.chatClient = chatClient;
}
public String send(String message) {
return chatClient
.prompt()
.user(message)
.advisors(spec -> spec.param("conversationId", sessionId))
.call()
.content();
}
}
// Usage
ChatClient client = ChatClient.builder(chatModel)
.defaultAdvisors(
MessageChatMemoryAdvisor.builder(chatMemory).build()
)
.build();
ConversationSession session1 = new ConversationSession("session-1", client);
ConversationSession session2 = new ConversationSession("session-2", client);
session1.send("I like cats");
session2.send("I like dogs");
session1.send("What animal do I like?"); // Returns "cats"
session2.send("What animal do I like?"); // Returns "dogs"