Get ahead
VMware offers training and certification to turbo-charge your progress.
Learn moreOn 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.
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:
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:
SessionEvent. Other event types, with names you can define yourself, can record what else happened in the same log.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.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.VectorStoreChatMemoryAdvisorstays, since sessions have no semantic recall counterpart yet. The advisor context keyChatMemory.CONVERSATION_IDnow has a permanent home asMemoryAdvisor.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
DocumentPostProcessor,Audio
spring-ai-autoconfigure-model-audio-observation module can log the text and transcripts, which is off by default.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.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).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.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).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.DefaultUsage.fromJson supports the legacy generationTokens again, tolerates a native usage that can't be serialized, and UsageCalculator handles null usage.SimpleVectorStore filters.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.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.
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.
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
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.
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!