Spring Boot auto-configuration for chat memory functionality in Spring AI applications
Production-ready examples for common use cases.
A web application where multiple users have independent conversations with an AI assistant.
@Service
public class MultiUserChatService {
@Autowired
private ChatMemory chatMemory;
@Autowired
private AIService aiService;
public ChatResponse handleUserMessage(String userId, String message) {
String conversationId = "user:" + userId;
// Add user message
chatMemory.add(conversationId, new UserMessage(message));
// Get conversation history
List<Message> history = chatMemory.get(conversationId);
// Generate AI response with context
String aiResponse = aiService.generateResponse(history);
// Store AI response
chatMemory.add(conversationId, new AssistantMessage(aiResponse));
return new ChatResponse(aiResponse, conversationId);
}
public void initializeUserConversation(String userId, String userRole) {
String conversationId = "user:" + userId;
SystemMessage systemMessage = new SystemMessage(
"You are assisting a " + userRole + " with technical questions. " +
"Provide clear, concise answers."
);
chatMemory.add(conversationId, systemMessage);
}
public List<Message> getUserHistory(String userId) {
return chatMemory.get("user:" + userId);
}
public void clearUserHistory(String userId) {
chatMemory.clear("user:" + userId);
}
}A customer support system that maintains context across multiple support sessions.
@Service
public class CustomerSupportService {
@Autowired
private ChatMemory chatMemory;
@Autowired
private CustomerRepository customerRepository;
@Autowired
private TicketService ticketService;
public SupportResponse handleSupportMessage(String ticketId, String message) {
String conversationId = "ticket:" + ticketId;
// Initialize conversation with customer context if new
if (chatMemory.get(conversationId).isEmpty()) {
initializeSupportConversation(ticketId);
}
// Add customer message
chatMemory.add(conversationId, new UserMessage(message));
// Get full conversation history
List<Message> history = chatMemory.get(conversationId);
// Generate contextual response
String response = generateSupportResponse(history, ticketId);
// Store assistant response
chatMemory.add(conversationId, new AssistantMessage(response));
// Update ticket
ticketService.updateTicket(ticketId, message, response);
return new SupportResponse(response, ticketId);
}
private void initializeSupportConversation(String ticketId) {
Ticket ticket = ticketService.getTicket(ticketId);
Customer customer = customerRepository.findById(ticket.getCustomerId());
String systemContext = String.format(
"You are a customer support agent. " +
"Customer: %s (Account: %s, Tier: %s). " +
"Issue Category: %s. " +
"Be professional, empathetic, and solution-focused.",
customer.getName(),
customer.getAccountNumber(),
customer.getTier(),
ticket.getCategory()
);
String conversationId = "ticket:" + ticketId;
chatMemory.add(conversationId, new SystemMessage(systemContext));
// Add ticket history if exists
if (ticket.getPreviousMessages() != null) {
List<Message> previousMessages = ticket.getPreviousMessages();
chatMemory.add(conversationId, previousMessages);
}
}
public void archiveResolvedTicket(String ticketId) {
String conversationId = "ticket:" + ticketId;
List<Message> messages = chatMemory.get(conversationId);
// Archive to long-term storage
ticketService.archiveConversation(ticketId, messages);
// Clear from active memory
chatMemory.clear(conversationId);
}
}An AI assistant that helps developers with code reviews, maintaining context across multiple review sessions.
@Service
public class CodeReviewAssistant {
@Autowired
private ChatMemory chatMemory;
@Autowired
private CodeAnalysisService codeAnalysisService;
public ReviewResponse reviewCode(String pullRequestId, String code, String question) {
String conversationId = "pr:" + pullRequestId;
// Initialize with PR context if new
if (chatMemory.get(conversationId).isEmpty()) {
initializeReviewContext(pullRequestId);
}
// Add code snippet as user message
UserMessage codeMessage = UserMessage.builder()
.text("Review this code:\n```\n" + code + "\n```\n\nQuestion: " + question)
.metadata(Map.of("codeLength", code.length(), "language", detectLanguage(code)))
.build();
chatMemory.add(conversationId, codeMessage);
// Analyze code
CodeAnalysis analysis = codeAnalysisService.analyze(code);
// Generate review with context
List<Message> history = chatMemory.get(conversationId);
String review = generateReview(history, analysis);
// Store review
chatMemory.add(conversationId, new AssistantMessage(review));
return new ReviewResponse(review, analysis.getIssues());
}
private void initializeReviewContext(String pullRequestId) {
PullRequest pr = getPullRequest(pullRequestId);
String systemContext = String.format(
"You are a senior code reviewer. " +
"PR: %s - %s. " +
"Language: %s. " +
"Focus on: code quality, security, performance, and best practices. " +
"Be constructive and provide specific suggestions.",
pr.getTitle(),
pr.getDescription(),
pr.getLanguage()
);
chatMemory.add("pr:" + pullRequestId, new SystemMessage(systemContext));
}
public void finalizePullRequest(String pullRequestId, boolean approved) {
String conversationId = "pr:" + pullRequestId;
List<Message> reviewHistory = chatMemory.get(conversationId);
// Save review history to PR
savePullRequestReview(pullRequestId, reviewHistory, approved);
// Clear active memory
chatMemory.clear(conversationId);
}
}An AI tutor that adapts to student learning pace and maintains learning context.
@Service
public class EducationalTutorService {
@Autowired
private ChatMemory chatMemory;
@Autowired
private StudentProgressService progressService;
public TutorResponse handleStudentQuestion(String studentId, String subject, String question) {
String conversationId = "student:" + studentId + ":subject:" + subject;
// Initialize learning session if new
if (chatMemory.get(conversationId).isEmpty()) {
initializeLearningSession(studentId, subject);
}
// Add student question
chatMemory.add(conversationId, new UserMessage(question));
// Get learning history
List<Message> history = chatMemory.get(conversationId);
// Generate adaptive response
StudentProgress progress = progressService.getProgress(studentId, subject);
String response = generateAdaptiveResponse(history, progress);
// Store tutor response
chatMemory.add(conversationId, new AssistantMessage(response));
// Update progress
progressService.updateProgress(studentId, subject, question, response);
return new TutorResponse(response, progress.getLevel());
}
private void initializeLearningSession(String studentId, String subject) {
Student student = getStudent(studentId);
StudentProgress progress = progressService.getProgress(studentId, subject);
String systemContext = String.format(
"You are a patient and encouraging tutor for %s. " +
"Student level: %s. " +
"Learning style: %s. " +
"Adapt explanations to their level. Use examples and analogies. " +
"Encourage questions and provide positive reinforcement.",
subject,
progress.getLevel(),
student.getLearningStyle()
);
String conversationId = "student:" + studentId + ":subject:" + subject;
chatMemory.add(conversationId, new SystemMessage(systemContext));
}
public void endLearningSession(String studentId, String subject) {
String conversationId = "student:" + studentId + ":subject:" + subject;
List<Message> sessionHistory = chatMemory.get(conversationId);
// Analyze session for insights
progressService.analyzeSession(studentId, subject, sessionHistory);
// Keep session for reference (don't clear immediately)
// TTL or manual cleanup can handle old sessions
}
}An assistant that helps users analyze and query documents with conversational context.
@Service
public class DocumentAnalysisService {
@Autowired
private ChatMemory chatMemory;
@Autowired
private DocumentProcessor documentProcessor;
@Autowired
private VectorStore vectorStore;
public AnalysisResponse analyzeDocument(String sessionId, MultipartFile document, String query) {
String conversationId = "doc-session:" + sessionId;
// Process document on first upload
if (chatMemory.get(conversationId).isEmpty()) {
initializeDocumentSession(sessionId, document);
}
// Add user query
chatMemory.add(conversationId, new UserMessage(query));
// Retrieve relevant document sections
List<String> relevantSections = vectorStore.similaritySearch(query, 5);
// Get conversation history
List<Message> history = chatMemory.get(conversationId);
// Generate analysis with context
String analysis = generateDocumentAnalysis(history, relevantSections);
// Store analysis
chatMemory.add(conversationId, new AssistantMessage(analysis));
return new AnalysisResponse(analysis, relevantSections);
}
private void initializeDocumentSession(String sessionId, MultipartFile document) {
// Process document
DocumentMetadata metadata = documentProcessor.extractMetadata(document);
String documentSummary = documentProcessor.generateSummary(document);
// Store in vector database
vectorStore.addDocument(sessionId, document);
// Initialize conversation with document context
String systemContext = String.format(
"You are analyzing a document: '%s' (%s, %d pages). " +
"Summary: %s. " +
"Answer questions based on the document content. " +
"Cite specific sections when possible.",
metadata.getTitle(),
metadata.getType(),
metadata.getPageCount(),
documentSummary
);
String conversationId = "doc-session:" + sessionId;
chatMemory.add(conversationId, new SystemMessage(systemContext));
}
public void closeDocumentSession(String sessionId) {
String conversationId = "doc-session:" + sessionId;
List<Message> sessionHistory = chatMemory.get(conversationId);
// Archive session
archiveSession(sessionId, sessionHistory);
// Cleanup
chatMemory.clear(conversationId);
vectorStore.deleteDocument(sessionId);
}
}Process multiple conversations efficiently in batch operations.
@Service
public class BatchConversationProcessor {
@Autowired
private ChatMemory chatMemory;
@Autowired
private ChatMemoryRepository repository;
public void processBatchMessages(Map<String, List<Message>> conversationBatches) {
conversationBatches.forEach((conversationId, messages) -> {
// Batch add is more efficient than individual adds
chatMemory.add(conversationId, messages);
});
}
public Map<String, Integer> getConversationSizes() {
List<String> conversationIds = repository.findConversationIds();
return conversationIds.parallelStream()
.collect(Collectors.toMap(
id -> id,
id -> repository.findByConversationId(id).size()
));
}
public void cleanupInactiveConversations(Duration inactivityThreshold) {
List<String> conversationIds = repository.findConversationIds();
Instant cutoff = Instant.now().minus(inactivityThreshold);
conversationIds.parallelStream()
.filter(id -> isInactive(id, cutoff))
.forEach(repository::deleteByConversationId);
}
private boolean isInactive(String conversationId, Instant cutoff) {
List<Message> messages = repository.findByConversationId(conversationId);
if (messages.isEmpty()) return true;
// Check last message timestamp from metadata
Message lastMessage = messages.get(messages.size() - 1);
Object timestamp = lastMessage.getMetadata().get("timestamp");
if (timestamp instanceof Instant) {
return ((Instant) timestamp).isBefore(cutoff);
}
return false;
}
public void exportConversations(List<String> conversationIds, Path exportPath) {
conversationIds.forEach(id -> {
List<Message> messages = chatMemory.get(id);
exportConversation(id, messages, exportPath);
});
}
}Handle high-throughput conversations asynchronously.
@Service
public class AsyncConversationService {
@Autowired
private ChatMemory chatMemory;
private final ExecutorService executor = Executors.newFixedThreadPool(20);
public CompletableFuture<String> handleMessageAsync(String conversationId, String message) {
return CompletableFuture.supplyAsync(() -> {
// Add user message
chatMemory.add(conversationId, new UserMessage(message));
// Get history
List<Message> history = chatMemory.get(conversationId);
// Generate response
String response = generateResponse(history);
// Store response
chatMemory.add(conversationId, new AssistantMessage(response));
return response;
}, executor);
}
public CompletableFuture<Void> handleMultipleMessagesAsync(
Map<String, String> conversationMessages) {
List<CompletableFuture<Void>> futures = conversationMessages.entrySet()
.stream()
.map(entry -> handleMessageAsync(entry.getKey(), entry.getValue())
.thenAccept(response -> logResponse(entry.getKey(), response)))
.collect(Collectors.toList());
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
}
@PreDestroy
public void shutdown() {
executor.shutdown();
try {
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
}
}Migrate conversations between different repository implementations.
@Service
public class ConversationMigrationService {
@Autowired
@Qualifier("sourceRepository")
private ChatMemoryRepository sourceRepository;
@Autowired
@Qualifier("targetRepository")
private ChatMemoryRepository targetRepository;
public MigrationResult migrateAllConversations() {
List<String> conversationIds = sourceRepository.findConversationIds();
int total = conversationIds.size();
int successful = 0;
int failed = 0;
for (String conversationId : conversationIds) {
try {
migrateConversation(conversationId);
successful++;
} catch (Exception e) {
logger.error("Failed to migrate conversation: {}", conversationId, e);
failed++;
}
}
return new MigrationResult(total, successful, failed);
}
private void migrateConversation(String conversationId) {
List<Message> messages = sourceRepository.findByConversationId(conversationId);
if (!messages.isEmpty()) {
targetRepository.saveAll(conversationId, messages);
logger.info("Migrated conversation {} ({} messages)",
conversationId, messages.size());
}
}
public void verifyMigration() {
List<String> sourceIds = sourceRepository.findConversationIds();
List<String> targetIds = targetRepository.findConversationIds();
Set<String> missingIds = new HashSet<>(sourceIds);
missingIds.removeAll(targetIds);
if (!missingIds.isEmpty()) {
logger.warn("Missing conversations in target: {}", missingIds);
}
// Verify message counts
for (String id : sourceIds) {
int sourceCount = sourceRepository.findByConversationId(id).size();
int targetCount = targetRepository.findByConversationId(id).size();
if (sourceCount != targetCount) {
logger.warn("Message count mismatch for {}: source={}, target={}",
id, sourceCount, targetCount);
}
}
}
}tessl i tessl/maven-org-springframework-ai--spring-ai-autoconfigure-model-chat-memory@1.1.0