Add AI Chat Memory and Redis Persistence to Spring Boot using LangChain4j
This guide shows how to integrate LangChain4j with Alibaba Baichuan's DashScope model in a Spring Boot application, enabling per‑user AI chat memory identified by @MemoryId, isolating conversations, and persisting the sliding‑window message history to Redis with TTL handling.
Scenario
Integrate LangChain4j in a Spring Boot project to connect to Alibaba Baichuan (DashScope) and provide AI chat memory, conversation isolation, and persistent storage of chat history in Redis.
AI Conversation Persistence Flow
User request → Controller → AI Service (annotated with @MemoryId) → ChatMemory (loads history from Redis) → DashScope model → Answer → ChatMemory updates and saves back to Redis.
Key Concepts
@MemoryId: placed on a method parameter to distinguish sessions; the framework fetches the corresponding history from ChatMemoryStore. ChatMemory: manages a list of messages for a single session, supports a sliding window (e.g., keep the latest N messages). ChatMemoryStore: persistence interface; implementation stores the message list in an external medium such as Redis. MessageWindowChatMemory: sliding‑window implementation that discards oldest messages when the limit is exceeded. ChatMessageSerializer: provided by LangChain4j to convert List<ChatMessage> to JSON and correctly handle polymorphic types (SystemMessage, UserMessage, AiMessage). AiServices: core of the framework; generates implementations via dynamic proxy and weaves memory, tool calls, etc.
Environment Preparation
JDK 17+, Redis (Windows version tporadowski/redis 5.0+ works without RedisJSON), Maven 3.6+, an Alibaba Cloud Baichuan account with an API Key, and an activated model service such as qwen-max.
Project Dependency Configuration (pom.xml)
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
</parent>
<groupId>com.example</groupId>
<artifactId>spring-langchain4j-bailian</artifactId>
<version>1.0</version>
<properties>
<java.version>17</java.version>
<langchain4j.version>1.0.0-beta3</langchain4j.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-community-bom</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<!-- Alibaba Baichuan DashScope integration starter -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-community-dashscope-spring-boot-starter</artifactId>
</dependency>
<!-- Explicit DashScope core dependency (starter may not transit) -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-community-dashscope</artifactId>
</dependency>
<!-- Spring Data Redis support -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
</dependencyManagement>No extra Jackson or langchain4j-jackson dependencies are required; the framework already includes necessary serialization support.
Configuration File (application.yml)
langchain4j:
community:
dashscope:
chat-model:
api-key: ${DASHSCOPE_API_KEY} # store API key in env variable
model-name: qwen-max # choose model from Baichuan model marketplace
log-requests: true # enable request logging for debugging
log-responses: true # enable response logging
spring:
data:
redis:
host: localhost
port: 6379
password: 123456 # set if Redis requires a password
database: 0 # Redis database indexCode Implementation
Redis Storage Implementation (Core)
Create RedisChatMemoryStore.java that implements ChatMemoryStore and uses the official serializer.
package com.badao.ai.config;
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.ChatMessageDeserializer;
import dev.langchain4j.data.message.ChatMessageSerializer;
import dev.langchain4j.store.memory.chat.ChatMemoryStore;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
@Slf4j
@Component
public class RedisChatMemoryStore implements ChatMemoryStore {
private static final String KEY_PREFIX = "chat:memory:";
private static final long TTL_SECONDS = 3600; // 1 hour expiration
private final StringRedisTemplate redisTemplate;
public RedisChatMemoryStore(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
private String getKey(Object memoryId) {
return KEY_PREFIX + memoryId;
}
@Override
public List<ChatMessage> getMessages(Object memoryId) {
String key = getKey(memoryId);
String json = redisTemplate.opsForValue().get(key);
if (json == null || json.isBlank()) {
return new ArrayList<>();
}
try {
// ✅ Use the official deserialization method
return ChatMessageDeserializer.messagesFromJson(json);
} catch (Exception e) {
log.error("Deserialization failed, memoryId: {}, json: {}", memoryId, json, e);
redisTemplate.delete(key);
return new ArrayList<>();
}
}
@Override
public void updateMessages(Object memoryId, List<ChatMessage> messages) {
String key = getKey(memoryId);
try {
// ✅ Use the official serialization method
String json = ChatMessageSerializer.messagesToJson(messages);
redisTemplate.opsForValue().set(key, json, TTL_SECONDS, TimeUnit.SECONDS);
log.debug("Stored conversation successfully, memoryId: {}, message count: {}", memoryId, messages.size());
} catch (Exception e) {
log.error("Serialization failed, memoryId: {}", memoryId, e);
}
}
@Override
public void deleteMessages(Object memoryId) {
String key = getKey(memoryId);
redisTemplate.delete(key);
log.info("Deleted conversation memory, memoryId: {}", memoryId);
}
}The serializer internally uses Jackson and correctly handles polymorphic ChatMessage types. Setting a TTL prevents Redis from being filled with stale sessions.
Configure ChatMemoryProvider
Inject RedisChatMemoryStore into a provider that creates a MessageWindowChatMemory per memoryId with a maximum of 10 recent messages.
import dev.langchain4j.memory.chat.ChatMemoryProvider;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AiConfig {
@Bean
public ChatMemoryProvider chatMemoryProvider(RedisChatMemoryStore redisChatMemoryStore) {
// Each memoryId gets an isolated ChatMemory linked to Redis storage
return memoryId -> MessageWindowChatMemory.builder()
.id(memoryId)
.chatMemoryStore(redisChatMemoryStore)
.maxMessages(10) // keep the latest 10 messages
.build();
}
} MessageWindowChatMemoryholds messages in memory but persists them through the injected ChatMemoryStore. The framework automatically calls updateMessages after each interaction.
AI Service Interface
Define an assistant interface with @AiService and mark the session identifier with @MemoryId.
package com.badao.ai.service;
import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.spring.AiService;
@AiService
public interface Assistant {
@SystemMessage("你是一位知识渊博的AI助手,请用中文友好地回答用户的问题。")
String chat(@MemoryId Long memoryId, @UserMessage String userMessage);
} @AiServicelets the framework generate an implementation at runtime and inject the appropriate ChatMemory. The @MemoryId argument determines which stored conversation is used.
Controller
Expose a REST endpoint that reads the user ID from a request header and forwards it as the memory identifier.
package com.badao.ai.controller;
import com.badao.ai.service.Assistant;
import org.springframework.web.bind.annotation.*;
@RestController
public class AssistantController {
private final Assistant assistant;
public AssistantController(Assistant assistant) {
this.assistant = assistant;
}
@GetMapping("/ai/assistant")
public String chat(@RequestHeader("X-User-Id") Long userId,
@RequestParam(value = "message") String message) {
// Pass userId as memoryId to the AI service
return assistant.chat(userId, message);
}
}Testing and Verification
Start Redis.
Run the Spring Boot application (ensure DASHSCOPE_API_KEY environment variable is set).
Send a request, e.g.,
curl -H "X-User-Id: 2" "http://localhost:885/ai/assistant?message=我叫霸道的程序猿".
Stop the service and inspect Redis – the conversation data should be stored.
Restart the application and ask the same question; the assistant should recall the previous name, confirming persistence.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
The Dominant Programmer
Resources and tutorials for programmers' advanced learning journey. Advanced tracks in Java, Python, and C#. Blog: https://blog.csdn.net/badao_liumang_qizhi
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
