Spring AI 2.1.0-M1 landed on September 25, 2026, built against Spring Boot 4.2.0-M2. It's a milestone, so it isn't meant for production yet, but it previews three changes that matter if you're building AI features on the JVM: a structured message model, support for OpenAI's Responses API, and a way to store embeddings you computed elsewhere. Here's a practical walkthrough of each.
Why This Release Is Worth a Look
Most AI integrations start simple: send text, get text back. Then you add images, tool calls, and reasoning models, and a flat "message with a text field" stops describing what the model actually returned. Spring AI 2.1 addresses that directly, and it also catches up with how OpenAI now expects reasoning models to be called.
1. Messages Are Now Ordered Parts
Messages in 2.1 carry an ordered list of MessagePart entries instead of a single blob of text plus side lists. The part types are TextPart, ReasoningPart, ToolCallPart, ToolResultPart, MediaPart, and UnknownPart.
Order matters because modern models interleave content: a bit of reasoning, a tool call, more reasoning, then an answer. Keeping that sequence means a conversation can round-trip to the provider faithfully, which is important when you replay history on the next turn.
Building a multimodal prompt becomes explicit:
UserMessage question = UserMessage.builder()
.part(TextPart.of("What changed between these two charts?"))
.part(MediaPart.of(q2Chart))
.part(MediaPart.of(q3Chart))
.build();
Existing code keeps working: accessors like getText() and getToolCalls() are now views over the parts, so you can migrate gradually.
2. OpenAI Responses API Support
The new OpenAiResponsesChatModel talks to OpenAI's /v1/responses endpoint. The motivation is practical: per the release announcement, starting with GPT-5.4, Chat Completions doesn't support tool calling combined with a reasoning effort other than none. If you want a reasoning model that also calls your tools, you need the Responses API.
Switching is a single property:
spring.ai.openai.api-key=${OPENAI_API_KEY}
spring.ai.openai.chat.api=responses
Because the switch happens at the model layer, code written against ChatClient doesn't need to change:
@Service
public class ReportAssistant {
private final ChatClient chatClient;
public ReportAssistant(ChatClient.Builder builder) {
this.chatClient = builder
.defaultSystem("You are a concise financial reporting assistant.")
.build();
}
public String summarize(String reportText) {
return chatClient.prompt()
.user(u -> u.text("Summarize the key risks in this report:\n{report}")
.param("report", reportText))
.call()
.content();
}
}
The implementation is deliberately stateless, so you still own conversation history (for example with Spring AI's chat memory support). OpenAiResponsesChatOptions exposes Responses-specific settings such as reasoning effort, structured output, and hosted tools like web search, file search, code interpreter, and image generation.
3. Upserting Pre-Computed Embeddings
Until now, VectorStore.add() embedded documents for you using the configured EmbeddingModel. That's convenient, but many teams already produce embeddings in a separate pipeline, such as a batch job or a data platform, and re-embedding everything inside the application is wasteful.
2.1 adds VectorStore.upsert(), which takes EmbeddedDocument objects that pair a Document with a vector you supply. It has replace-by-id semantics: an existing document with the same id is replaced, otherwise a new one is inserted. Initial support covers pgvector, Redis, Elasticsearch, and Qdrant.
@Component
public class EmbeddingImporter {
private final VectorStore vectorStore;
private final ExternalEmbeddingClient embeddings; // your own pipeline client
public EmbeddingImporter(VectorStore vectorStore, ExternalEmbeddingClient embeddings) {
this.vectorStore = vectorStore;
this.embeddings = embeddings;
}
public void importArticles(List<Article> articles) {
var batch = articles.stream()
.map(a -> new EmbeddedDocument(
new Document(a.id(), a.summary(), Map.of("source", a.url())),
embeddings.vectorFor(a.id())))
.toList();
vectorStore.upsert(batch);
}
}
One thing to watch: the vectors must come from the same embedding model (and dimension) your query side uses, or similarity search results will be meaningless.
Trying the Milestone
Milestones aren't on Maven Central, so add the Spring milestone repository and use the BOM:
<repositories>
<repository>
<id>spring-milestones</id>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>2.1.0-M1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Since this milestone targets Spring Boot 4.2, try it on a branch that also moves to the Boot 4.2 milestone rather than mixing it into a production service.
Other Changes Worth Knowing
- MCP servers can be customized with
McpStatelessSyncServerCustomizerandMcpStatelessAsyncServerCustomizerbeans. - The Redis vector store accepts custom metadata fields.
- Timeout configuration for
spring.ai.openai.timeoutandspring.ai.openai.chat.timeoutwas fixed.
Separately, the Spring team announced a new release rhythm: the whole portfolio now ships patch releases on a single "Patch Thursday" each month, driven by a surge in security reports. If you maintain Spring services, it's a predictable date to plan dependency updates around.
Key Takeaways
- Messages are now ordered lists of typed parts, preserving interleaved reasoning and tool calls; old accessors still work.
spring.ai.openai.chat.api=responsesswitches to OpenAI's Responses API, needed for reasoning models that also call tools.VectorStore.upsert()withEmbeddedDocumentlets you store externally computed embeddings with replace-by-id semantics.- 2.1.0-M1 targets Spring Boot 4.2 and is a milestone: experiment on a branch, not in production.
