Migrating from 1.x to 2.x
|
This content was generated with the assistance of AI and should be verified against the official documentation of each tool and framework before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
Written against Spring AI 2.0.1, Spring Boot 4.1.x and Java 21 (verified 2026-09-29), from the official Upgrade Notes as they stood on that date, checked against the 2.0.1 jars and, where marked, by running code. Spring AI version 2.0.0 became generally available on 2026-06-12 and 2.0.1 is the current stable release. The 2.0 line requires Java 17 or later, Spring Boot 4.0 or 4.1 with Spring Framework 7, Jackson 3 and JSpecify nullability, so a Spring AI upgrade is also a Spring Boot upgrade, and the Boot 4 migration is the larger part of the work for most projects. This page is the reference to keep beside the compiler while you upgrade: the rows are the changes DocsAssistant-style code runs into, each with a link to the note it comes from. The rest of the sub-section describes 2.0.1 as if it were the only version; this page is the one place that looks back. A 2.1 milestone exists on Maven Central, but nothing here is based on it. The most common renames also fit in one box of the printable Spring AI Cheat Sheet, and the version baseline on the landing page lists every version this sub-section targets.
How to upgrade
-
Green tests first. An upgrade of this size needs a baseline. Tests with a scripted
ChatModel(see Testing) are ideal, since they do not change when the provider does. -
Boot 4 and Java. Move the platform first or together with the BOM; Spring AI 2.0.x supports Spring Boot 4.0.x and 4.1.x. Jackson 3 changes the
ObjectMapperpackage totools.jackson.databindand the builder toJsonMapper. -
BOM. Import
spring-ai-bom2.0.1 (or the latest 2.0.x). Allorg.springframework.aiartifacts take their version from it. -
Recipes. Run the OpenRewrite recipes in the next section, then read the diff.
-
Compile. Work through the tables below.
-
Properties. Start the application with a configuration check; unknown keys under
spring.aiare silently ignored by Boot, so compare your files with the tables, or run the recipe that renames them. -
Behaviour. Read the "compiles but behaves differently" table. These are the changes no compiler will report.
OpenRewrite recipes
Three sets of recipes exist. Two are from Spring AI and cover the MCP changes of 2.0.0-M3; one is a community recipe that covers most of the mechanical 2.0 changes.
| Recipe | What it does | Source |
|---|---|---|
|
Upgrades |
Arconia: Spring AI 2.0 (community project) |
|
Removes the |
|
|
Moves the |
same file |
|
Rewrites |
same file |
|
Umbrella recipe that applies all of the M3 recipes above |
same file |
The recipe names were read from the community documentation and from the YAML file in the Spring AI repository on 2026-09-29; the recipes themselves were not run for this page. The Spring AI ones are invoked like this, with the plugin version the notes use:
mvn org.openrewrite.maven:rewrite-maven-plugin:6.32.0:run \
-Drewrite.configLocation=https://raw.githubusercontent.com/spring-projects/spring-ai/refs/heads/main/src/rewrite/migrate-to-2-0-0-M3.yaml \
-Drewrite.activeRecipes=org.springframework.ai.migration.M3MigrateMcpAnnotations \
-Dmaven.compiler.failOnError=false
Source: Spring AI: Upgrade Notes (the M3 recipes and the plugin version).
The community recipe is run like this:
./mvnw -U org.openrewrite.maven:rewrite-maven-plugin:run \
-Drewrite.recipeArtifactCoordinates=io.arconia.migrations:rewrite-spring:LATEST \
-Drewrite.activeRecipes=io.arconia.rewrite.spring.ai2.UpgradeSpringAi_2_0
Source: Arconia Migrations: Spring AI 2.0.
Two cautions. A recipe changes source files, so run it on a clean branch and read every hunk; LATEST in the second command floats, so pin a version for a repeatable run. And the M3 note warns that a pom.xml that declares mcp-spring-webflux without a version, relying
on a BOM, may fail to parse before the recipe can run, which is why that note tells you to patch those files first. The same community project has io.arconia.rewrite.spring.ai1.UpgradeSpringAi_1_0 for the older 0.8 to 1.0 jump
(Arconia: Spring AI 1.0); the Spring AI notes link to it for the 1.0.0-RC1 upgrade.
Modules and dependencies
| Old (1.x) | New (2.0.1) | Notes |
|---|---|---|
|
|
|
|
Same package, now in the renamed |
Read from the 1.0.3 sources and the 2.0.1 jars, not listed in the notes; see Retrieval & RAG Bridge |
|
|
|
|
|
|
|
Removed; the Oracle project provides it |
|
Azure Cosmos DB vector store and chat memory |
External module maintained by the Cosmos DB team |
|
|
Removed |
|
MiniMax dedicated support |
Use the Anthropic module against MiniMax’s Anthropic-compatible URL |
|
|
|
|
|
|
|
|
|
|
|
|
Read from the 2.0.1 jars, not listed in the notes; see Production & Deployment |
Evaluators such as |
Package |
Read from the 2.0.1 jars; see Evaluation |
Jackson 2 ( |
Jackson 3 ( |
|
OpenSearch client 2.x, Testcontainers class |
OpenSearch client 3.6, |
Options and properties
| Old (1.x) | New (2.0.1) | Notes |
|---|---|---|
Mutable options, |
Strictly immutable options; start a builder from one with |
|
|
|
|
Defaults set in the property classes |
Defaults set in the options constructors |
|
|
|
|
|
Pass the builder: |
|
|
|
|
|
|
|
|
Typed overloads: |
|
|
Not public API any more; and |
The 2.0 properties that are new and worth adopting are spring.ai.chat.client.tool-calling.enabled, spring.ai.chat.client.tool-search-advisor., spring.ai.tools.limits., spring.ai.tools.throw-exception-on-error and
spring.ai.tools.resolution.fallback.enabled. The full list for your version is in the starters' configuration metadata.
Tools
| Old (1.x) | New (2.0.1) | Notes |
|---|---|---|
Each |
The loop lives in |
|
|
|
|
|
|
|
|
|
|
|
Attach a |
|
|
Removed; use |
|
|
|
|
|
Removed; conversation history belongs to the advisors |
|
Every tool definition sent on every call |
New and optional: |
|
|
Removed |
The @Tool and ToolCallbackProvider model itself did not change; see Tool Calling and ToolCallingAdvisor & Tool Search.
ChatClient, advisors and memory
| Old (1.x) | New (2.0.1) | Notes |
|---|---|---|
|
No default conversation ID; pass |
|
|
|
|
|
|
|
Memory advisor default order |
|
|
JDBC repository table without a sequence column |
|
|
|
Oldest first like every other repository; remove any reversal workaround |
|
|
Auto-registers |
|
|
Unchanged in 2.0. The community |
|
Marker-less advisors |
New marker interfaces |
See Chat Memory, Chat Memory Repositories and Advisors for the 2.0.1 behaviour.
Structured output, MCP, audio and observability
| Old (1.x) | New (2.0.1) | Notes |
|---|---|---|
|
Override |
|
MCP Java SDK 1.x |
SDK 2.0.0: server tool-input validation on by default ( |
|
|
Required fields up front: |
|
|
|
|
|
|
|
|
Only |
|
|
|
|
|
|
|
Tool span |
Span |
|
Provider-specific cache numbers in metadata |
|
Provider changes
| Old (1.x) | New (2.0.1) | Notes |
|---|---|---|
|
|
|
Anthropic |
4096; set it explicitly if you relied on the old cap to bound cost |
same |
OpenAI tool schemas |
|
|
Default temperature 0.7 supplied by the starters |
Removed; each provider’s default applies, so set |
|
Mistral moderation |
Split into |
|
|
New package |
Same code, different behaviour
These changes need no edit to compile, and each can change what a running application does. Check every row against your code.
| Change | Effect | Notes |
|---|---|---|
Conversation ID mandatory in the built-in memory advisors |
Calls that relied on a default ID now throw; a hard-coded ID that used to be shared silently is gone |
|
Memory advisor outside the tool loop |
Tool-call and tool-result messages are no longer written to the repository; only the final user and assistant messages are |
|
Tool fallback resolution off (2.0.1) |
A tool registered only as a bean can no longer be called unless attached to the request; the call fails closed |
|
Tool call limits (2.0.1) |
150 calls per turn and 40 per tool by default; an agent that legitimately needs more must raise them |
|
Usage accumulated over the tool loop (2.0.1) |
Token counts after a tool exchange are now the total, so exact-count assertions and cost dashboards change |
|
No default temperature |
Answers from a model whose own default is 1.0 vary more than they did at 0.7 |
|
OpenAI |
Schema adherence is checked less strictly; replies can be longer and cost more |
see the provider table |
MCP server validates tool input |
Clients that sent loosely typed arguments now get error results |
|
|
A checked exception no longer becomes an error message for the model |
|
HTTP transport header names lower-cased in the WebMvc MCP transport |
A custom |
|
OpenAI-compatible |
A base URL without |
verified on 2.0.1 |
Advisor default order |
They sit inside the tool loop, so retrieval runs again on every pass of the loop; give them an order before |
verified on 2.0.1 |
Migrating code from the books
Two books in this guide’s bibliography were written against versions before 2.0. Their concepts are sound; their code needs the changes below. The snippets are shapes of the API those versions used, written for this page (not taken from the books), and are not compiled.
Spring AI in Action (Spring AI 1.0.3, Spring Boot 3.5)
Spring AI in Action is close to 2.0 in style: fluent ChatClient, advisors, tools with @Tool, MCP. Most of the migration is the tables above. Its examples typically look like this:
ChatClient client = ChatClient.builder(chatModel)
.defaultAdvisors(PromptChatMemoryAdvisor.builder(memory).conversationId("session-1").build())
.defaultOptions(OpenAiChatOptions.builder().temperature(0.2).build())
.defaultToolNames("currentWeather")
.build();
Source: Spring AI: Upgrade Notes.
and the 2.0.1 form is:
package com.example.docsassistant;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.client.advisor.ToolCallingAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.ai.model.tool.ToolExecutionEligibilityChecker;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.function.FunctionToolCallback;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/** The 2.0.1 form of the code the older books and 1.x tutorials show. */
@Configuration(proxyBeanMethods = false)
class MigratedDocsAssistant {
record WeatherRequest(String city) { }
record WeatherResponse(String summary) { }
/** Was: a raw java.util.function.Function bean found by name. Now: an explicit ToolCallback. */
@Bean
ToolCallback currentWeather() {
return FunctionToolCallback.builder("currentWeather",
(WeatherRequest request) -> new WeatherResponse("sunny in " + request.city()))
.description("Get the weather in a city")
.inputType(WeatherRequest.class)
.build();
}
/** Was: an advisor created with .conversationId("...") or PromptChatMemoryAdvisor. */
@Bean
ChatClient memoryClient(ChatModel chatModel, ChatMemory chatMemory, ToolCallback currentWeather) {
return ChatClient.builder(chatModel)
.defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())
.defaultTools(currentWeather) // defaultToolCallbacks(...) is deprecated for removal in 2.0
.build();
}
String ask(ChatClient client, String conversationId, String question) {
return client.prompt()
.user(question)
// options() takes the builder, not a built object; the fields you set override the model's start-up options,
// and a request's options replace (not merge with) any defaultOptions(...) on the client
.options(OpenAiChatOptions.builder().temperature(0.2))
// the conversation id is required and supplied per call; derive it from the authenticated user, never from client input alone
.advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId))
.call()
.content();
}
/** Was: options.copy() / fromOptions(). Options are immutable; mutate() starts a builder from one. */
ChatOptions lowerTemperature(ChatOptions options) {
return options.mutate().temperature(0.0).build();
}
/** Was: internalToolExecutionEnabled(false). Now: decide in the advisor when the loop continues. */
@Bean
ToolExecutionEligibilityChecker stopOnFinishReasonStop() {
return response -> response != null && response.hasToolCalls()
&& !"stop".equals(response.getResult().getMetadata().getFinishReason());
}
ToolCallingAdvisor insideTheLoopHistory() {
return ToolCallingAdvisor.builder().disableInternalConversationHistory().build();
}
}
Source: Spring AI: Upgrade Notes.
The class compiled against 2.0.1. It is not a program to run; each method shows one change:
-
the
Functionbean found by name becomes an explicitToolCallbackand is attached to the client; -
the memory advisor is
MessageChatMemoryAdvisor, with the conversation ID passed on each call; -
options(…)takes the builder; -
mutate()replacescopy(); -
a
ToolExecutionEligibilityCheckerandToolCallingAdvisor.builder()replaceinternalToolExecutionEnabled.
Other points for the Spring AI in Action examples, in the book’s own order of topics:
| Topic | What to change |
|---|---|
Getting started, Azure OpenAI |
|
Prompts, options |
Options are builders; |
RAG |
Same concepts, builders and packages; rename the |
Memory |
Add a conversation ID to every call; |
Tools |
|
MCP |
Annotations moved to |
Observability, security |
Update dashboards for |
Agents, Embabel |
The book’s Embabel chapter predates the 1.x releases; see Agentic Patterns |
Mastering Spring AI (Spring AI 0.8.x)
Mastering Spring AI predates the fluent ChatClient, advisors, chat memory and MCP; it is a good source for concepts, prompt types and governance, and almost none of its code compiles today. Migrating from it is a rewrite in the direction of the 1.0 API followed by the 2.0 tables. These renames happened
across the 0.8 to 1.0 milestones, before the current upgrade notes begin; treat the table as a map, and confirm each name in the 2.0.1 reference before relying on it.
BeanOutputParser<Actor> parser = new BeanOutputParser<>(Actor.class);
String format = parser.getFormat();
ChatResponse response = chatClient.call(new Prompt(question + format));
Actor actor = parser.parse(response.getResult().getOutput().getContent());
@Bean
FunctionCallback weather() {
return FunctionCallbackWrapper.builder(new WeatherService())
.withName("CurrentWeather").withDescription("Get the weather").build();
}
Source: Spring AI: Upgrade Notes (the 1.0.0-M7 and 1.0.0-RC1 sections list the renames).
| 0.8.x API | 2.0.1 form | Notes |
|---|---|---|
|
|
rename |
|
|
rename |
|
|
rename |
|
|
rename |
|
|
|
Manual history in the prompt |
|
new since the book |
|
|
the old model-level interface became |
Starter names like |
|
|
Milestone and snapshot repositories in the POM |
Not needed: 2.0.x is on Maven Central |
see Setup & Starters |
|
|
Boot 3 and later |
Function calling by name in the prompt options |
Tools attached to the request |
see the Tools table |
Governance, LLMOps, scaling chapter |
Concepts still apply; see Production & Deployment for the current APIs |
Verify the upgrade
Compiling is not enough. After the mechanical changes:
-
Run the scripted-model tests: they catch wiring, advisor order and tool-loop changes without a provider.
-
Run the evaluation set against the same model before and after, and compare answers, latency, token use and cost; see Evaluation. Answers change with the default temperature and with structured-output schema changes even when the model is the same.
-
Check the metrics and dashboards listed in Observability for the renamed tool span and the accumulated usage.
-
Read the startup log for deprecation warnings on
.optionsproperty paths and fix them now; the deprecated forms exist to ease the transition. -
Re-test the security paths from Security & Guardrails: per-request tool registration,
@PreAuthorizeon tools and the conversation ID derived from the token.