Real PostgreSQL 16 and Redis Stack 7.4; 24 tests write output/01-24. Covers the 20-message default window with no property, the 36-character conversation_id, tool messages dropped by JdbcChatMemoryRepository, concurrent add() on one conversation, a 1.x table under the 2.0 repository, the Redis repository silently backing off for a custom ChatMemory, and the removal of PromptChatMemoryAdvisor. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Ja4jkzrbQ4LQZBNrb5mkZE
4.6 KiB
chat-memory
Companion code for Chat Memory in Spring AI 2.0: JDBC, Redis and Windowed Conversations, part of the Spring AI series on ankurm.com.
MessageChatMemoryAdvisor + MessageWindowChatMemory + a ChatMemoryRepository (in-memory, PostgreSQL through JDBC, or Redis Stack), with per-user conversation IDs, a token-budget memory of our own, and every trap the article describes reproduced by a test that writes its own transcript.
There is no live model anywhere: ScriptedChatModel records the prompts it is sent, which is what a memory test needs to prove -- which messages reached the model. The databases are real.
Versions
| Component | Version |
|---|---|
| Spring Boot | 4.1.1 |
| Spring AI | 2.0.1 (GA 2026-06-12 for 2.0.0; 2.0.1 on Maven Central 2026-08-20) |
| PostgreSQL | 16.13 |
| Redis Stack | 7.4.7 (RediSearch 2.10.20, RedisJSON) |
| Jedis (pulled in by the Redis starter) | 7.4.1 |
| Java | 25 (LTS) |
Quickstart
scripts/services-up.sh # PostgreSQL 16 (apt), Redis Stack (docker), plain Redis (apt) -- optional
scripts/run-all.sh # runs all 24 tests and regenerates output/01 .. 24
Tests that need a service which is not listening are skipped with a message, not failed, so the in-memory tests still run with nothing installed. Run the app itself with mvn spring-boot:run (in-memory), -Dspring-boot.run.profiles=jdbc or =redis; it needs OPENAI_API_KEY for real answers.
What's here
| File | What it shows |
|---|---|
config/MemoryConfig.java |
The ChatMemory bean (window size is not configurable by property) and the ChatClient with MessageChatMemoryAdvisor |
config/RedisMemoryConfig.java |
Why the Redis repository is built by hand: the autoconfigured one silently steps aside for a custom ChatMemory |
memory/ConversationRegistry.java |
Who owns which conversation; app.ownership.enforce=false reproduces the leak |
memory/ConversationService.java |
Passing ChatMemory.CONVERSATION_ID per request |
memory/TokenBudgetChatMemory.java |
A ChatMemory that prunes by tokens instead of by message count |
web/ChatController.java |
Four endpoints; the user comes from an X-User header only so the tests need no login |
application*.yml |
Profiles jdbc and redis; the default excludes both repository autoconfigurations |
src/broken/PromptAdvisorFrom1x.java |
Not compiled by the build; scripts/capture-1x-compile.sh compiles it against 2.0.1 and 1.1.8 |
Output files
Every file is written by the test named in the right column (or by the script), and every console block in the article is quoted from one of them.
| File | Written by |
|---|---|
01-window-semantics.txt |
WindowSemanticsTest |
02-advisor-conversations.txt, 03-missing-conversation-id.txt |
AdvisorConversationTest |
04-autoconfigured-defaults.txt, 05-no-window-property.txt |
DefaultWindowTest |
06-token-growth.txt |
TokenGrowthTest |
07-token-budget-memory.txt |
TokenBudgetChatMemoryTest |
08 .. 14 (JDBC round trip, 36-character limit, dropped tool messages, lost update, overlapping saves, 1.x table upgrade, locked fix) |
JdbcPostgresTest |
15-redis-round-trip.txt, 16-redis-lost-update.txt |
RedisStackTest |
17-redis-silent-fallback.txt |
RedisSilentFallbackTest |
18-redis-ttl-and-cap.txt |
RedisCapsAndTtlTest |
19-redis-plain-fails.txt |
RedisPlainFailureTest |
20-ownership-enforced.txt, 21-ownership-unenforced.txt |
OwnershipTest, OwnershipLeakTest |
22-config-keys.txt |
ConfigKeysTest |
23-prompt-advisor-removed.txt |
scripts/capture-1x-compile.sh |
24-memory-with-tool-calling.txt |
ToolCallingMemoryTest |
Two consecutive mvn test runs produce byte-identical files. 12-jdbc-overlapping-saves.txt deliberately prints no exact count: two saveAll calls that overlap produce 3 or 6 messages depending on timing, and the file says so instead of pinning one run.
Requirements
JDK 25, Maven. PostgreSQL 16 and Redis Stack are optional (see above); Docker is only used to run Redis Stack.