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.
183 lines
8.7 KiB
Markdown
183 lines
8.7 KiB
Markdown
# 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`](../src/test/java/com/ankurm/ai/ChatMemoryMigrationTest.java).
|
|
|
|
---
|
|
|
|
## 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:
|
|
|
|
```xml
|
|
<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:
|
|
|
|
```java
|
|
// 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 `*Options` builders 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 `.options` prefix is gone** for every non-chat model type — embedding, image, audio,
|
|
moderation, OCR. `spring.ai.openai.embedding.options.model` becomes
|
|
`spring.ai.openai.embedding.model`.
|
|
- Default values are **no longer duplicated** in `*Properties` classes; they live at the options
|
|
level. If you were reading a default off a `*Properties` bean, it may now be `null`.
|
|
- `spring.ai.<provider>.chat.internal-tool-execution-enabled` — **removed** (see §4).
|
|
- The `streamToolCallResponses` properties 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 all `ChatModel`
|
|
implementations.
|
|
|
|
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:
|
|
|
|
- **`streamToolCallResponses`** from `ToolCallingAdvisor.Builder`, `ToolCallAdvisor.Builder` and
|
|
`ToolSearchToolCallingAdvisor.Builder`. The upgrade notes are unusually direct about why: the
|
|
design flaw behind it could not be fixed without a breaking change to `ChatClientResponse`, so
|
|
the option was removed outright rather than patched.
|
|
- **`SpringBeanToolCallbackResolver`**, and with it the whole pattern of declaring bare
|
|
`Function` / `Supplier` / `Consumer` beans and referencing them by name.
|
|
- **`toolNames()`** from all chat options classes and from `ChatClient`.
|
|
|
|
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` |
|
|
|
|
```java
|
|
// 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_ID` as a value.**
|
|
> `CONVERSATION_ID` survives, but it is the *metadata key* used to pass an id. The removed
|
|
> `DEFAULT_CONVERSATION_ID` was 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 `required` array.
|
|
- Properties annotated `@JsonProperty(required = false)` — **including** `@JsonProperty` without an
|
|
explicit `required` — 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 `ModelOptionsUtils` members are **removed**, because model options no longer depend on
|
|
Jackson directly.
|
|
- MCP `elicit(…)` overloads on `McpAsyncRequestContext` / `McpSyncRequestContext` that took
|
|
`tools.jackson.core.type.TypeReference<T>` now take
|
|
**`org.springframework.core.ParameterizedTypeReference<T>`**.
|
|
|
|
## 8. Migration order that works
|
|
|
|
1. **Boot 3 → Boot 4** on its own, with Spring AI still on 1.1.x. Land it.
|
|
2. Bump the Spring AI BOM to 2.0.0 and fix compile errors: options builders, `toolNames()`,
|
|
`internalToolExecutionEnabled`, module renames.
|
|
3. Fix chat memory — every advisor call site needs a conversation ID. Grep for
|
|
`DEFAULT_CONVERSATION_ID` and for advisor construction.
|
|
4. Re-test structured output against the real model. Schema changes are silent.
|
|
5. 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.
|