Upgrading to Spring AI 2.0 breaks chat memory code twice. First, ChatMemory.DEFAULT_CONVERSATION_ID and MessageChatMemoryAdvisor.builder().conversationId() stop compiling with cannot find symbol. Delete those lines, run the app, and the first call throws java.lang.IllegalArgumentException: conversationId cannot be null.
As of Spring AI 2.0, memory advisors have no default conversation ID. Every call must pass one through
ChatMemory.CONVERSATION_ID.
The fix is.advisors(a -> a.param(ChatMemory.CONVERSATION_ID, id))with an ID scoped to the user or session.
The same change shipped in the 1.1.6 patch release. Code that relied on the 1.1.5 defaultdefaultmixes every user’s history into one conversation.
Environment and test setup
I ran the same code against Spring AI 1.1.5, 1.1.6, and 2.0.2, in that order. The LLM was qwen3:4b on a local Ollama server, so you can reproduce everything without a paid API key.
| Item | Version |
|---|---|
| Spring Boot (reproduce and fix) | 4.1.2 |
| Spring AI (reproduce and fix) | 2.0.2 |
| Spring Boot (1.x comparison) | 3.5.16 |
| Spring AI (1.x comparison) | 1.1.5 (run), 1.1.6 (compile) |
| Ollama | 0.40.11 |
| Model | qwen3:4b |
| JDK | 21.0.12.1 (Temurin) |
| Maven | 3.10.0 |
The ChatMemory bean is the one Spring AI auto-configures. According to the official docs, that is MessageWindowChatMemory backed by InMemoryChatMemoryRepository. Every run is a CommandLineRunner with no web server: it asks a few questions and exits.
Reproduce: 1.x chat memory code breaks twice on Spring AI 2.0.2
I moved one piece of 1.x code through 1.1.5, 1.1.6, and 2.0.2 to see exactly what changes at each step.
Spring AI 1.1.5: the code runs, but two users share one conversation
The starting point is the pattern you see in most 1.x code. The memory advisor gets a single fixed conversation ID when it is built.
// Spring AI 1.x style: one fixed conversation id on the advisor
@Bean
CommandLineRunner runner(ChatClient.Builder builder, ChatMemory chatMemory) {
return args -> {
ChatClient chatClient = builder
.defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory)
.conversationId(ChatMemory.DEFAULT_CONVERSATION_ID)
.build())
.build();
ask(chatClient, "alice", "Hi, my name is Alice. Reply with one short greeting without emoji.");
ask(chatClient, "bob", "What is my name? Answer in one short sentence.");
System.out.println("messages in '" + ChatMemory.DEFAULT_CONVERSATION_ID + "' = "
+ chatMemory.get(ChatMemory.DEFAULT_CONVERSATION_ID).size());
};
}
ask() is a helper that prints the question and the answer. Its first argument, alice or bob, is only a label for the output. That mimics service code that never tells users apart. In 1.1.5 you get the same result even without .conversationId(), because the builder already defaults to ChatMemory.DEFAULT_CONVERSATION_ID (value "default").
[alice] Q: Hi, my name is Alice. Reply with one short greeting without emoji.
[alice] A: Hi Alice.
[bob] Q: What is my name? Answer in one short sentence.
[bob] A: Your name is Alice.
messages in 'default' = 4
No exception is thrown. But when Bob asks for his name, the model answers with Alice’s. All four messages from both users landed in the single default conversation. The 2.0 change exists to stop exactly this.
Spring AI 1.1.6 and 2.0.2: cannot find symbol DEFAULT_CONVERSATION_ID
Next, I compiled the same source against Spring AI 1.1.6. The only change was overriding the pom’s spring-ai.version with -Dspring-ai.version=1.1.6.
$ cd ai1 && mvn -B -q -Dmaven.repo.local=../.tools/m2/repository -Dspring-ai.version=1.1.6 clean compile
[ERROR] COMPILATION ERROR :
[ERROR] /home/user/project/ai1/src/main/java/com/example/memorydemo/MemoryDemoApplication.java:[24,55] cannot find symbol
symbol: variable DEFAULT_CONVERSATION_ID
location: interface org.springframework.ai.chat.memory.ChatMemory
To rule out a stale build, I ran dependency:list and confirmed the build resolved spring-ai-client-chat and spring-ai-model at 1.1.6. So a patch bump from 1.1.5 to 1.1.6 breaks compilation. The 1.1.6 release notes list it as "Chat memory advisors now require an explicit conversation ID".
Spring Boot 4.1.2 with Spring AI 2.0.2 gives the same error for the same source. It appears on all three lines that use the constant (lines 24, 31, and 32).
$ cd ai2-legacy && mvn -B -q -Dmaven.repo.local=../.tools/m2/repository compile
[ERROR] COMPILATION ERROR :
[ERROR] /home/user/project/ai2-legacy/src/main/java/com/example/memorydemo/MemoryDemoApplication.java:[24,55] cannot find symbol
symbol: variable DEFAULT_CONVERSATION_ID
location: interface org.springframework.ai.chat.memory.ChatMemory
...
Replacing the constant reveals method conversationId(java.lang.String)
javac doesn’t report the .conversationId() problem on the first pass. It fails to resolve the argument first, so it never gets as far as checking the method. If you follow the first hint in the upgrade notes and replace the constant with its value "default", the next error shows up.
$ cd ai2-literal && mvn -B -q -Dmaven.repo.local=../.tools/m2/repository compile
[ERROR] COMPILATION ERROR :
[ERROR] /home/user/project/ai2-literal/src/main/java/com/example/memorydemo/MemoryDemoApplication.java:[24,29] cannot find symbol
symbol: method conversationId(java.lang.String)
location: class org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor.Builder
The builder method itself is gone. Renaming the constant is not enough. Fixing one error at a time costs you two compile rounds, so fix both together.
Removing the builder line: conversationId cannot be null at runtime
To get past the compiler, the obvious move is to drop .conversationId() and call the model as before. That is the missing mode in the ai2/ project.
// Spring AI 2.0: no default conversation id on the advisor
ChatClient chatClient = builder
.defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())
.build();
// Call without a conversation id (1.x code with .conversationId() simply removed)
void missing(ChatClient chatClient) {
String answer = chatClient.prompt()
.user("Hi, my name is Alice. Reply with one short greeting without emoji.")
.call()
.content();
System.out.println("answer = " + answer);
}
This compiles. At runtime it fails on the very first call.
java.lang.IllegalArgumentException: conversationId cannot be null
at org.springframework.util.Assert.notNull(Assert.java:182) ~[spring-core-7.0.10.jar:7.0.10]
at org.springframework.ai.chat.client.advisor.api.BaseChatMemoryAdvisor.getConversationId(BaseChatMemoryAdvisor.java:41) ~[spring-ai-client-chat-2.0.2.jar:2.0.2]
at org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor.before(MessageChatMemoryAdvisor.java:75) ~[spring-ai-client-chat-2.0.2.jar:2.0.2]
at org.springframework.ai.chat.client.advisor.api.BaseAdvisor.adviseCall(BaseAdvisor.java:51) ~[spring-ai-client-chat-2.0.2.jar:2.0.2]
...
at com.example.memorydemo.MemoryDemoApplication.missing(MemoryDemoApplication.java:42) ~[classes/:na]
The stack trace ends in MessageChatMemoryAdvisor.before(). That is the step where the advisor reads memory, before any request goes to the model. This is why the output has no answer = line. Because the exception escapes the CommandLineRunner, Spring Boot logs Application run failed and the Maven plugin exits with code 1.
Cause: Spring AI 2.0 requires a conversation ID on every call
The Spring AI upgrade notes describe the change in four items.
| Change | 1.1.5 | 2.0.2 and 1.1.6 |
|---|---|---|
ChatMemory.DEFAULT_CONVERSATION_ID |
Constant with value "default" |
Removed, compile error |
.conversationId(String) builder |
Sets a default ID when the advisor is built | Removed from MessageChatMemoryAdvisor and VectorStoreChatMemoryAdvisor |
getConversationId() |
Two arguments (Map, String) |
One argument (Map), throws if the ID is missing |
PromptChatMemoryAdvisor |
Available | Removed in 2.0, deprecated in 1.1.6 |
The exception message comes straight from BaseChatMemoryAdvisor in 2.0.2. It reads the ChatMemory.CONVERSATION_ID key from the advisor context, and Assert.notNull fails when the value is null.
default String getConversationId(Map<String, @Nullable Object> context) {
Assert.notNull(context, "context cannot be null");
Assert.noNullElements(context.keySet().toArray(), "context cannot contain null keys");
Assert.notNull(context.get(ChatMemory.CONVERSATION_ID), "conversationId cannot be null");
return context.get(ChatMemory.CONVERSATION_ID).toString();
}
I pulled this code from spring-ai-client-chat-2.0.2-sources.jar in the lab. In the 1.1.5 source, the same method was getConversationId(Map, String). When the context had no key, it returned the second argument as the default ID, and the builder’s default was ChatMemory.DEFAULT_CONVERSATION_ID. In 2.0 that default is gone, so the caller must always supply the ID.

Fix: pass a per-user CONVERSATION_ID with advisors param
Build the advisor without a conversation ID, then pass one through ChatMemory.CONVERSATION_ID on every call. In this example the user name doubles as the ID.
static void ask(ChatClient chatClient, String conversationId, String question) {
String answer = chatClient.prompt()
.user(question)
.advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId))
.call()
.content();
System.out.println("[" + conversationId + "] Q: " + question);
System.out.println("[" + conversationId + "] A: " + answer);
}
The key line is .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId)). The value goes into the advisor context, and getConversationId() reads it from there. I sent the same questions as in the 1.1.5 run, this time in the order alice, bob, alice.
// Fix: pass a conversation id per user on every call
void fixed(ChatClient chatClient, ChatMemory chatMemory) {
ask(chatClient, "alice", "Hi, my name is Alice. Reply with one short greeting without emoji.");
ask(chatClient, "bob", "What is my name? Answer in one short sentence.");
ask(chatClient, "alice", "What is my name? Answer in one short sentence.");
System.out.println("messages in 'alice' = " + chatMemory.get("alice").size());
System.out.println("messages in 'bob' = " + chatMemory.get("bob").size());
}
mode = fixed
[alice] Q: Hi, my name is Alice. Reply with one short greeting without emoji.
[alice] A: Hello Alice.
[bob] Q: What is my name? Answer in one short sentence.
[bob] A: I don't know your name because I don't have access to personal information about you.
[alice] Q: What is my name? Answer in one short sentence.
[alice] A: Your name is Alice.
messages in 'alice' = 4
messages in 'bob' = 2
The exact LLM wording can change from run to run. What matters is who knows whose name and how many messages each conversation holds. Bob now says he doesn’t know his name. Alice gets her name back from her own history. Memory holds 4 messages under alice and 2 under bob, compared with 4 messages piled into a single default conversation on 1.1.5.

How to build conversation IDs in a real service
Don’t use a display name as the ID like this demo does. The official Chat Memory docs say to build the ID on the server from the current user or session. Their example is currentUser.getId() + ":" + httpSession.getId().
- Give every user a different ID. If one user can have several conversations, split by conversation too
- Build the ID from server-side authentication data, not from a value the client sends
- Scope listing and deleting conversations to the current user’s IDs
Pitfalls: don’t settle for a fix that only compiles
A fixed string brings back the 1.1.5 problem
The fastest fix is to pass a fixed string such as "default" on every call. It compiles and the exception disappears. But just like the 1.1.5 run, every user shares one conversation. The official docs also recommend deriving the ID from the user or session instead of using a fixed, shared value.
If you ran a version with a builder default, like 1.1.5, and either skipped .conversationId() or used the constant, your memory store may already contain mixed history. With a persistent store such as JDBC, check what has accumulated under the default conversation. After you switch to the new ID scheme, decide what to do with those records.
Custom advisors: getConversationId(context, default) no longer compiles
If you have a custom advisor that implements BaseChatMemoryAdvisor, it needs one more change. Per the upgrade notes, the two-argument getConversationId(Map, String) became the one-argument getConversationId(Map). Update overrides and call sites to the one-argument form, and make sure the context contains an ID before the call.
Replace PromptChatMemoryAdvisor at the same time
PromptChatMemoryAdvisor was removed in 2.0. The upgrade notes point you to MessageChatMemoryAdvisor, which has the same builder API. The difference is that it adds history as a list of messages instead of system prompt text. VectorStoreChatMemoryAdvisor also lost its .conversationId() builder method.
A 1.1.x patch bump to 1.1.6 hits the same error
You don’t have to move to 2.0 to run into this. In my runs, 1.1.5 executed the 1.x code unchanged and 1.1.6 failed to compile it. If a dependency bot bumps patch versions in your project, that update PR can break the build.
At the time of writing, Maven Central also lists 1.1.7, 1.1.8, and 2.1.0-RC1. I didn’t run those versions in the lab, so check the chat memory section of the upgrade notes again before you move to them.
Summary
- In Spring AI 2.0.2 and 1.1.6,
ChatMemory.DEFAULT_CONVERSATION_IDand the.conversationId()builder are gone, so 1.x code fails to compile - Deleting the builder line leads to
IllegalArgumentException: conversationId cannot be null, thrown before the model is called - Passing the ID on every call with
.advisors(a -> a.param(ChatMemory.CONVERSATION_ID, id))fixes it - On 1.1.5 the
defaultconversation mixed two users’ history. A fixed string keeps that problem - Also check custom advisors for the new
getConversationId()signature and replacePromptChatMemoryAdvisor
References
- Spring AI Upgrade Notes: Chat Memory Advisors Conversation ID Is Now Required
- Spring AI Upgrade Notes: Removed ChatMemory.DEFAULT_CONVERSATION_ID
- Spring AI Chat Memory: Working with Conversation IDs
- BaseChatMemoryAdvisor.java (spring-ai v2.0.2)
- BaseChatMemoryAdvisor.java (spring-ai v1.1.5)
- MessageChatMemoryAdvisor.java (spring-ai v1.1.5)
- Spring AI v1.1.6 release notes
- Require explicit conversation ID in chat memory advisors (commit 13cde41)
- spring-ai-client-chat versions (Maven Central)