Companion code for the ankurm.com guide. Verified on Spring AI 2.0.0, Spring Boot 4.1.0,
JDK 25.0.3. run.txt is unedited mvn test output: 5 tests, 0 failures.
Runs with NO API key, no network and no provider account. A canned ChatModel returns fixed
responses; ChatClient, advisors and chat memory all sit above the model, so everything that
changed is still exercised faithfully. TestChatModel records each Prompt it receives, which
lets the tests assert what Spring AI SENT rather than what a model replied.
docs/01 complete breaking-change reference: platform (Boot 4 mandatory, Jackson 2 to 3),
artifact renames (spring-ai-advisors-vector-store -> spring-ai-vector-store-advisor,
spring-ai-core split, OCI and Minimax removals, MCP transports moved into Spring AI),
options builders replacing setters, the dropped .options property prefix, tool
calling (internalToolExecutionEnabled and toolNames removed outright, not renamed),
chat memory, structured output schema changes, and a migration order that works.
Tested here
- Chat memory becomes stricter: 2.0 removes the remaining default-ID and builder-based
configuration paths (ChatMemory.DEFAULT_CONVERSATION_ID and .conversationId() are both
gone). Explicit per-request conversation ids - introduced during the 1.x line and already
the recommended approach - become the only migration target.
- CONVERSATION_ID survives as the metadata KEY. It and the removed DEFAULT_CONVERSATION_ID
look interchangeable and are opposites; swapping them to clear a compile error restores
exactly the shared-conversation behaviour that removing it was meant to take away.
- PromptChatMemoryAdvisor is gone; MessageChatMemoryAdvisor replaces it, and the difference
is not cosmetic (system-prompt text vs real Message objects, so token counts change).
- A test asserts two conversation ids cannot see each other.
8.7 KiB
Spring AI 1.x → 2.0: complete breaking-change reference
Compiled from the official upgrade notes and verified against spring-ai-bom:2.0.0 on
Spring Boot 4.1.0 / JDK 25. Items marked [tested] are asserted in
ChatMemoryMigrationTest.
0. Platform — do this first, it gates everything
| 1.x | 2.0 | |
|---|---|---|
| Spring Boot | 3.x | 4.x only |
| Jackson | 2 (com.fasterxml.jackson) |
3 (tools.jackson) |
| Java | 17 | 17 minimum, 21+ recommended |
Spring AI 2.0 is built on the Boot 4 dependency model and cannot be loaded in a Boot 3 context. There is no partial upgrade: you migrate Boot 3 → 4 first, or not at all.
Spring AI is not managed by the Spring Boot BOM. Import its own:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>2.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
The Jackson 2 → 3 move is the biggest hidden cost. It changes the default date format and property order, so anything that snapshot-tests JSON, or persists serialized model output, needs full integration testing rather than a compile check.
1. Artifact and module changes
| Change | Action |
|---|---|
spring-ai-core ended at 1.0.0-M6 |
The project is split into focused modules — spring-ai-model, spring-ai-client-chat, etc. Depend on what you use. |
spring-ai-advisors-vector-store → spring-ai-vector-store-advisor |
Rename the dependency. |
spring-ai-spring-cloud-bindings removed |
Configure credentials with normal Boot property sources. |
MCP transports mcp-spring-webflux / mcp-spring-webmvc moved out of the MCP Java SDK into Spring AI |
Update coordinates; they are now Spring AI artifacts. |
| OCI GenAI removed from the main repository | Migrate to the separate OCI GenAI integration repo. |
| Minimax dedicated support removed | Use the Anthropic support, per Minimax's own recommendation. Minimax embeddings are no longer supported at all. |
2. Options: setters are gone, builders only
Every setX(...) on options classes is deprecated or removed:
// Before
OpenAiChatOptions options = new OpenAiChatOptions();
options.setTemperature(0.7); // no longer compiles
// After
OpenAiChatOptions options = OpenAiChatOptions.builder()
.temperature(0.7)
.build();
Two more renames in the same area:
N()→n()in*Optionsbuilders and configuration properties, to match Java conventions.spring.ai.ollama.chat.think-option→spring.ai.ollama.chat.think.
ChatClient no longer accepts a pre-built ChatOptions instance. Use the builder lambda form.
3. Configuration properties
- The
.optionsprefix is gone for every non-chat model type — embedding, image, audio, moderation, OCR.spring.ai.openai.embedding.options.modelbecomesspring.ai.openai.embedding.model. - Default values are no longer duplicated in
*Propertiesclasses; they live at the options level. If you were reading a default off a*Propertiesbean, it may now benull. spring.ai.<provider>.chat.internal-tool-execution-enabled— removed (see §4).- The
streamToolCallResponsesproperties on the tool-calling advisors — removed (see §4).
4. Tool calling — the largest semantic change
internalToolExecutionEnabled has been removed from ToolCallingChatOptions and every
provider-specific options class.
.internalToolExecutionEnabled(false)no longer compiles..internalToolExecutionEnabled(true)no longer compiles and the behaviour it named no longer exists — per-model internal tool execution has been removed from allChatModelimplementations.
This is not a rename. If you relied on the model driving the tool-call loop itself, that loop is gone and the orchestration moves up into the advisor layer.
Also removed:
streamToolCallResponsesfromToolCallingAdvisor.Builder,ToolCallAdvisor.BuilderandToolSearchToolCallingAdvisor.Builder. The upgrade notes are unusually direct about why: the design flaw behind it could not be fixed without a breaking change toChatClientResponse, so the option was removed outright rather than patched.SpringBeanToolCallbackResolver, and with it the whole pattern of declaring bareFunction/Supplier/Consumerbeans and referencing them by name.toolNames()from all chat options classes and fromChatClient.
Migrate name-based tool wiring to explicit ToolCallback instances.
5. Chat memory — [tested]
Three changes that interact, and the reason this repository exists:
| Change | Effect |
|---|---|
ChatMemory.DEFAULT_CONVERSATION_ID removed |
no more implicit "default" conversation |
Conversation ID no longer optional for MessageChatMemoryAdvisor / VectorStoreChatMemoryAdvisor |
must be supplied per request |
.conversationId(String) removed from those builders |
cannot be set at construction time |
PromptChatMemoryAdvisor removed |
use MessageChatMemoryAdvisor |
// After — the id is a per-request advisor param
client.prompt()
.user("what is my name?")
.advisors(a -> a.param(ChatMemory.CONVERSATION_ID, "user-42"))
.call()
.content();
Do not "fix" the compile error by reaching for
ChatMemory.CONVERSATION_IDas a value.CONVERSATION_IDsurvives, but it is the metadata key used to pass an id. The removedDEFAULT_CONVERSATION_IDwas the value"default". Same prefix, opposite meaning — and substituting one for the other restores exactly the shared-conversation behaviour that removing it was meant to take away.
What actually changed. Explicit per-request conversation IDs are not new — they were introduced during the 1.x line and were already the recommended approach. 2.0 removes the alternatives: the default-ID constant and the builder-based configuration path. The explicit per-request form is now the only way to do it, which makes it the migration target.
That matters because the default-ID path was easy to lean on. Omitting the id in 1.x compiled and
ran, with everything landing in a single shared "default" conversation — fine for a demo, wrong
for anything multi-user. Removing the fallback converts "I forgot" from silent behaviour into a
compile error. The test suite asserts that two conversation IDs cannot see each other.
PromptChatMemoryAdvisor → MessageChatMemoryAdvisor is also not cosmetic: the removed advisor
injected history into the system prompt as text, the replacement adds real Message
objects. Providers treat those differently, so expect prompt shape and token counts to change.
Chat memory repositories
Messages read from a ChatMemoryRepository (JDBC, Cassandra, MongoDB, Neo4j, Redis) now carry a
creation timestamp in metadata. Consequence: a retrieved message is not equals() to an
otherwise-identical message constructed in code. Anything comparing messages by value, or keying a
Set/Map on them, changes behaviour.
6. Structured output / BeanOutputConverter
- Kotlin properties optional in their primary constructor (nullable or defaulted) are no longer
in the JSON Schema
requiredarray. - Properties annotated
@JsonProperty(required = false)— including@JsonPropertywithout an explicitrequired— are no longer treated as required. BeanOutputConverter.postProcessSchema(JsonNode)removed. Subclasses overriding it will not compile.
The first two change the schema you send to the model, which can change model behaviour without any error. If you rely on the model populating a field, mark it required explicitly.
7. ModelOptionsUtils and MCP
- Several
ModelOptionsUtilsmembers are removed, because model options no longer depend on Jackson directly. - MCP
elicit(…)overloads onMcpAsyncRequestContext/McpSyncRequestContextthat tooktools.jackson.core.type.TypeReference<T>now takeorg.springframework.core.ParameterizedTypeReference<T>.
8. Migration order that works
- Boot 3 → Boot 4 on its own, with Spring AI still on 1.1.x. Land it.
- Bump the Spring AI BOM to 2.0.0 and fix compile errors: options builders,
toolNames(),internalToolExecutionEnabled, module renames. - Fix chat memory — every advisor call site needs a conversation ID. Grep for
DEFAULT_CONVERSATION_IDand for advisor construction. - Re-test structured output against the real model. Schema changes are silent.
- Integration-test anything that serializes: Jackson 3 changes date format and property order.
Steps 4 and 5 are the ones that do not announce themselves. Everything before them is a compiler error, which is the easy kind of breakage.