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.

The Dominant Programmer
The Dominant Programmer
The Dominant Programmer
Add AI Chat Memory and Redis Persistence to Spring Boot using LangChain4j

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 index

Code 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();
    }
}
MessageWindowChatMemory

holds 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);
}
@AiService

lets 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.

Redis stored chat memory screenshot
Redis stored chat memory screenshot
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Redisspring-bootAI chatbotLangChain4jAlibaba BaichuanConversation Memory
The Dominant Programmer
Written by

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

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.