1
0
Files
spring-ai-2-migration/docs/01-breaking-changes.md
Ankur cb777068a7 Spring AI 1.x to 2.0 migration: breaking-change reference and offline verification suite
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.
2026-08-01 10:51:02 +05:30

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-storespring-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 *Options builders and configuration properties, to match Java conventions.
  • spring.ai.ollama.chat.think-optionspring.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-enabledremoved (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
// 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.

PromptChatMemoryAdvisorMessageChatMemoryAdvisor 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.