# chat-memory Companion code for [Chat Memory in Spring AI 2.0: JDBC, Redis and Windowed Conversations](https://ankurm.com/spring-ai-2-0-chat-memory-jdbc-redis-windowed-conversations/), part of the [Spring AI series](../README.md) 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`](src/test/java/com/ankurm/chatmemory/support/ScriptedChatModel.java) 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 ```bash 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`](src/main/java/com/ankurm/chatmemory/config/MemoryConfig.java) | The `ChatMemory` bean (window size is not configurable by property) and the `ChatClient` with `MessageChatMemoryAdvisor` | | [`config/RedisMemoryConfig.java`](src/main/java/com/ankurm/chatmemory/config/RedisMemoryConfig.java) | Why the Redis repository is built by hand: the autoconfigured one silently steps aside for a custom `ChatMemory` | | [`memory/ConversationRegistry.java`](src/main/java/com/ankurm/chatmemory/memory/ConversationRegistry.java) | Who owns which conversation; `app.ownership.enforce=false` reproduces the leak | | [`memory/ConversationService.java`](src/main/java/com/ankurm/chatmemory/memory/ConversationService.java) | Passing `ChatMemory.CONVERSATION_ID` per request | | [`memory/TokenBudgetChatMemory.java`](src/main/java/com/ankurm/chatmemory/memory/TokenBudgetChatMemory.java) | A `ChatMemory` that prunes by tokens instead of by message count | | [`web/ChatController.java`](src/main/java/com/ankurm/chatmemory/web/ChatController.java) | Four endpoints; the user comes from an `X-User` header only so the tests need no login | | [`application*.yml`](src/main/resources) | Profiles `jdbc` and `redis`; the default excludes both repository autoconfigurations | | [`src/broken/PromptAdvisorFrom1x.java`](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.