Spring AI 2.1.0-RC1 and 2.0.2 Available Now

Releases | Eric Bottard | October 23, 2026 | 8 min read | ...

On behalf of the Spring AI team and everyone who has contributed, I'm happy to announce that Spring AI 2.1.0-RC1 and 2.0.2 have been released and are now available from Maven Central!

Release notes | Upgrade notes | Reference documentation

2.1.0-RC1 builds on 2.1.0-M1 and delivers most of what we promised in its "What's next" section: message parts are now produced and consumed natively by the major chat models, and session memory arrives in Spring AI itself. It also adds tool call confirmation, and extends the pre-computed embeddings support with search by query vector.

New in this release

Message Parts across providers

In M1, only OpenAiResponsesChatModel worked natively with MessagePart content. In this release the following ChatModel implementations produce and consume parts as well:

  • OpenAI (Chat Completions)
  • Anthropic
  • Google GenAI
  • Amazon Bedrock Converse
  • Ollama
  • Mistral AI
  • DeepSeek

Reasoning, tool calls, and provider-specific blocks therefore keep their order and their opaque payloads, such as Anthropic thinking signatures and Gemini thought signatures, across a whole conversation. Code that uses the existing accessors (getText(), getToolCalls(), ...) keeps working. The message builders' setters now replace parts in place, so UserMessage.builder().text(...) followed by another call no longer leaves stale parts behind.

Two related details for OpenAI-compatible endpoints. reasoning_content is echoed back on assistant messages because some endpoints, such as DeepSeek in thinking mode, require it, while others, such as Groq, reject it. Replaying is now configurable with spring.ai.openai.chat.replay-reasoning-content (or OpenAiChatOptions.replayReasoningContent); it keeps sending the property by default, and false turns it off. And on the Responses API, the order of the parts of a user message is preserved, and file URLs are sent as file_url.

Session Memory

ChatMemory stores a flat list of messages per conversation id. Long-running conversations and agents need more than that, so Spring AI now includes a session memory, ported from the community Spring AI Session project.

A session keeps a conversation as an append-only log of events and builds the prompt from a view of that log:

  • Structured events. Every message is a SessionEvent. Other event types, with names you can define yourself, can record what else happened in the same log.
  • Turn-aware compaction. Triggers such as TurnCountTrigger decide when the history has grown too large, and a strategy decides what leaves the view. WindowCompactionStrategy keeps the last events, the last turns, or fits a token budget, and an optional CompactionSummarizer (ChatModelSummarizer) can summarize what is dropped. A compaction always cuts on a turn boundary, so the model never sees a tool call without its result. Compaction never rewrites the log: it appends a boundary that defines the view from then on.
  • A repository SPI. InMemorySessionRepository is meant for tests, and JdbcSessionRepository supports PostgreSQL, MySQL, MariaDB and H2, with idempotent appends and version-checked compaction. A store of your own can be verified against the contract tests in spring-ai-test.

To use it with a ChatClient, add the spring-ai-starter-model-session-jdbc starter, register the session advisors, and pass a session id with every call:

@Bean
ChatClient chatClient(ChatModel chatModel, SessionService sessionService) {
    return ChatClient.builder(chatModel)
        .defaultAdvisors(SessionAdvisors.builder(sessionService)
            .compaction(new TurnCountTrigger(20), WindowCompactionStrategy.builder().lastTurns(10).build())
            .build())
        .build();
}

String answer = chatClient.prompt()
    .user("Hello!")
    .advisors(SessionAdvisors.session("session-abc"))
    .call()
    .content();

A session that does not exist yet is created on first use, and the session id is required on every call: there is deliberately no default session, since a shared one would silently merge the histories of different users. The advisors keep no state of their own, so any application instance can run any turn of a session.

ChatMemory, MessageWindowChatMemory, ChatMemoryRepository, MessageChatMemoryAdvisor, and the JDBC, Cassandra, Neo4j, MongoDB and Redis chat memory repositories are now deprecated, for removal in 3.0.0. Each deprecation names its replacement. Where there is no session counterpart yet (Cassandra, Neo4j, MongoDB, Redis, and the HSQLDB, Oracle, SQL Server and SQLite dialects), one is planned before 3.0.0. VectorStoreChatMemoryAdvisor stays, since sessions have no semantic recall counterpart yet. The advisor context key ChatMemory.CONVERSATION_ID now has a permanent home as MemoryAdvisor.CONVERSATION_ID, with the same value.

Tool Call Confirmation

Booking a flight, deleting a file or sending an email are actions a person may want to approve first. A tool can now ask for that:

class BookingTools {

    @Tool(description = "Book a flight", requiresConfirmation = true)
    String bookFlight(String flightNumber) {
        return bookingService.book(flightNumber);
    }
}

When the model calls such a tool, the tool-calling loop pauses before any call of the reply runs, and the reply goes back to your application listing the calls that wait for a decision. Your application collects a ToolCallDecision per call (allow, or deny with a message), runs the reply's calls with ToolCallingManager.executeToolCalls(prompt, reply, decisions), and sends the results as its next request. A denied call answers the model with your message instead of running.

The loop keeps no state while it waits: the pause is an ordinary response and the resumption an ordinary request, so this survives restarts as long as your application keeps the conversation. Which calls need confirmation is decided by a ToolCallConfirmationPolicy: the default reads the tool's metadata, forTools(...) names tools you do not own, such as MCP tools, and a lambda can inspect the arguments, for example to confirm only transfers over a given amount. Policies can be combined with or(...), and the spring.ai.tools.confirmation.* properties and ToolCallConfirmationPolicy beans are picked up by the auto-configuration. The feature is covered by integration tests against OpenAI, Gemini, Anthropic, Bedrock and Ollama. See Tool Call Confirmation in the reference documentation.

Searching with your own query embedding

M1 added VectorStore.upsert() to write vectors you computed yourself. The other half is now in place: VectorStoreRetriever gains similaritySearch(float[], SearchRequest), which takes the query vector from you. The request still supplies topK, the similarity threshold and the filter, the store's embedding model is never used, and a vector that is empty, contains NaN or infinity, or has the wrong length is rejected up front.

float[] queryEmbedding = ...; // computed elsewhere, by the model that embedded your documents
List<Document> results = vectorStore.similaritySearch(queryEmbedding,
        SearchRequest.builder().topK(5).similarityThreshold(0.7).build());

pgvector, Qdrant, Redis, and Elasticsearch implement it, and OpenSearchVectorStore now supports both upsert and query-embedding search as well (upsert requires manageDocumentIds, since OpenSearch otherwise generates the ids and cannot replace by id). Stores that do not support these operations now fail fast with an UnsupportedOperationException instead of calling the embedding model first.

The vector size check also got more robust: the expected width now comes from the configured dimensions or from the existing index, table or collection, and the embedding model is only consulted to create a new index. A temporary failure to read the index can no longer leave a store rejecting valid vectors for good. See Searching with a Query Embedding in the reference documentation.

Qxotic jinfer

Thanks to the Qxotic AI team, Spring AI now supports the pure-JVM inference engine jinfer. This comes in the form of

Audio

  • Google GenAI gains text-to-speech and transcription models.
  • Observability now covers speech and transcription (OpenAI, ElevenLabs) and Stability AI image generation, with Micrometer observations. A new spring-ai-autoconfigure-model-audio-observation module can log the text and transcripts, which is off by default.
  • Qxotic jinfer has a reference documentation section for its chat, embedding, and transcription support.

Smaller additions

  • TokenTextSplitter supports chunk overlap. It is opt-in: with the default overlap of 0, chunks are the same as before.
  • PgVectorStore can be pointed at an existing table with different column names, through contentFieldName, metadataFieldName and embeddingFieldName.
  • Azure OpenAI accepts custom credentials.
  • OpenAiChatModel populates rate-limit metadata, and custom headers are propagated for per-call requests and for embeddings.
  • BedrockProxyChatModel caches tool results, and BedrockChatOptions.requestParameters accepts nested values (see the upgrade notes below).
  • Anthropic prompt caching now also caches citation documents.
  • Anthropic streaming chunks include the thinking text.

Upgrade notes

A few changes may need a code change on your side. Details are in the upgrade notes:

  • BedrockChatOptions.requestParameters (and the matching Boot property) is now a Map<String, Object>. Source that assigns it to a Map<String, String> variable no longer compiles.
  • Neo4j chat memory index properties were fixed.
  • PgVectorStore no longer overwrites a document's own distance metadata. The computed value is available from Document#getScore().
  • ChatMemory and its related types are deprecated (see above).

Fixes

  • Anthropic: the answer is the primary generation, streaming requests stay in the caller's trace, and a replay block that gets dropped is logged.
  • OpenAI: image MIME types are detected for non-PNG formats in the Responses API, diarized transcription is handled correctly, an embedding response without an index falls back to the list position, and out-of-range usage counts fail with a clear message.
  • StructuredOutputValidationAdvisor no longer repeats tool call requests.
  • Google GenAI: tool calls are extracted correctly, and unset tool options are preserved.
  • Bedrock: tool-call arguments are no longer invalid JSON.
  • Ollama: responses without a message are handled.
  • DefaultUsage.fromJson supports the legacy generationTokens again, tolerates a native usage that can't be serialized, and UsageCalculator handles null usage.
  • Vector stores: Cassandra numeric filters, Qdrant fractional numbers in filters, and date normalization in SimpleVectorStore filters.
  • Document readers: TikaDocumentReader is re-entrant, the PDF readers close their resource stream, PDF text positions fall back to a merge sort, and JsoupDocumentReader no longer drops relative link URLs.

Dependencies and build

  • Spring Boot 4.2.0-RC1
  • MCP Java SDK 2.1.0
  • OpenAI Java SDK 4.80.0
  • Anthropic Java SDK 2.72.0
  • Google GenAI 1.73.0
  • AWS SDK and Bedrock runtime 2.55.14
  • Tika 4.0.0
  • Kotlin 2.4.21

From the Spring AI Community

A new version of the java client and Spring AI integration for the TypeSafe AI jev API is available.

Spring AI A2A 0.4.0 now uses 1.4.0.Final version of A2A Java SDK. Thanks to @deejay1 and @thjanssen for their contributions.

Spring AI 2.0.2

In addition to 2.1.0-RC1, 2.0.2 has already been released. All fixes in 2.0.2 are also included in 2.1.0-RC1.

View complete release notes →

Contributors

Thank you to everyone who worked on this release:

@56wj, @ashakirin, @Ashfaqbs, @chabinhwang, @chengwudi1, @chensishang, @CodeTrainerMan, @CryoThrust, @dimitarproynov, @dlwldn30, @flex-ilsub, @herder, @ikaitist, @iuliiasobolevska, @JamesBLewis, @jhpark1227, @jianran, @kezhenxu94, @lazer-dev, @Lubaoshuai, @martin-grofcik, @nicolaskrier, @OreOreDa, @pengjonas, @pengmoubuaixuexi, @Rainmemery, @rvullriede, @ryujungkyun, @sedhossein, @seo92js, @sharanrenukeshbharmshetty, @sobychacko, @ss-bhatt, @stack3mpty, @tzolov, @vinayreddykalluri, @wantaekchoi, @xujiantop-crypto, and @zxuhan

What's next

2.1.0 GA is next. Between now and then, we will focus on feedback on the features above, in particular session memory and the migration of chat models to message parts.

Get started

Try the release candidate in an existing application, or start a new one on start.spring.io. Session memory and tool call confirmation are new APIs, and we would like to hear how they fit your use cases before GA — open an issue if something is broken, or start a discussion to tell us what you would like to see next.

Cheers!

Resources

Project Page | GitHub | Issues | Reference documentation

Get the Spring newsletter

Stay connected with the Spring newsletter

Subscribe

Get ahead

VMware offers training and certification to turbo-charge your progress.

Learn more

Get support

TrueSource Spring Enterprise offers support and binaries for OpenJDK™, Spring, and Apache Tomcat® in one simple subscription.

Learn more

Upcoming events

Check out all the upcoming events in the Spring community.

View all