Spring AI Chat Client provides a fluent API for building AI-powered applications with LLMs, supporting advisors, streaming, structured outputs, and conversation memory
Enable multi-turn conversations with automatic history management.
Stores messages as structured Message objects.
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.memory.InMemoryChatMemory;
ChatMemory chatMemory = new InMemoryChatMemory();
ChatClient client = ChatClient.builder(chatModel)
.defaultAdvisors(
MessageChatMemoryAdvisor.builder(chatMemory).build()
)
.build();
// First message
client.prompt("My name is Alice").call().content();
// Second message - remembers previous
client.prompt("What is my name?").call().content();
// Response: "Your name is Alice"Use conversation IDs to separate user conversations:
// User 1
client.prompt()
.user("I like Java")
.advisors(spec -> spec.param("conversationId", "user-1"))
.call()
.content();
// User 2 (separate history)
client.prompt()
.user("I like Python")
.advisors(spec -> spec.param("conversationId", "user-2"))
.call()
.content();
// User 1 follow-up
client.prompt()
.user("What language do I like?")
.advisors(spec -> spec.param("conversationId", "user-1"))
.call()
.content();
// Response: "You like Java"Injects history as text in system prompt (more token-efficient).
import org.springframework.ai.chat.client.advisor.PromptChatMemoryAdvisor;
import org.springframework.ai.chat.prompt.PromptTemplate;
String template = """
You are a helpful assistant.
Conversation history:
{conversation_history}
""";
PromptChatMemoryAdvisor memoryAdvisor =
PromptChatMemoryAdvisor.builder(chatMemory)
.systemPromptTemplate(new PromptTemplate(template))
.build();
ChatClient client = ChatClient.builder(chatModel)
.defaultAdvisors(memoryAdvisor)
.build();MessageChatMemoryAdvisor:
PromptChatMemoryAdvisor:
public class ChatService {
private final ChatClient chatClient;
private final ChatMemory chatMemory = new InMemoryChatMemory();
public ChatService(ChatModel chatModel) {
this.chatClient = ChatClient.builder(chatModel)
.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();
}
public void clearHistory(String userId) {
chatMemory.clear(userId);
}
}For production, implement persistent storage:
import org.springframework.ai.chat.memory.ChatMemory;
public class RedisChatMemory implements ChatMemory {
private final RedisTemplate<String, List<Message>> redis;
@Override
public void add(String conversationId, List<Message> messages) {
String key = "chat:" + conversationId;
redis.opsForValue().set(key, messages, Duration.ofDays(7));
}
@Override
public List<Message> get(String conversationId, int lastN) {
String key = "chat:" + conversationId;
List<Message> messages = redis.opsForValue().get(key);
if (messages == null) return Collections.emptyList();
int start = Math.max(0, messages.size() - lastN);
return messages.subList(start, messages.size());
}
@Override
public void clear(String conversationId) {
redis.delete("chat:" + conversationId);
}
}public class LimitedChatMemory implements ChatMemory {
private final Map<String, List<Message>> conversations = new ConcurrentHashMap<>();
private final int maxMessages;
public LimitedChatMemory(int maxMessages) {
this.maxMessages = maxMessages;
}
@Override
public void add(String conversationId, List<Message> messages) {
conversations.compute(conversationId, (id, existing) -> {
List<Message> all = existing != null ?
new ArrayList<>(existing) : new ArrayList<>();
all.addAll(messages);
// Keep only last N messages
if (all.size() > maxMessages) {
return all.subList(all.size() - maxMessages, all.size());
}
return all;
});
}
// ... other methods
}Memory works automatically with streaming:
Flux<String> stream = client
.prompt("Continue our story")
.advisors(spec -> spec.param("conversationId", "user-123"))
.stream()
.content();
stream.subscribe(System.out::print);
// Memory saved after stream completes