mcpClients) {
return chatClientBuilder.defaultToolCallbacks(new SyncMcpToolCallbackProvider(mcpClients)).build();
}
```
To add OAuth2 to our MCP Client, we configure a Spring Security `SecurityFilterChain` to turn on OAuth2, as well as a custom `WebClient.Builder` used by the MCP client:
```java theme={null}
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http.authorizeHttpRequests(auth -> auth.anyRequest().permitAll())
.oauth2Client(Customizer.withDefaults())
.csrf(CsrfConfigurer::disable)
.build();
}
/**
* Overload Boot's default {@link WebClient.Builder}, so that we can inject an
* oauth2-enabled {@link ExchangeFilterFunction} that adds OAuth2 tokens to requests
* sent to the MCP server.
*/
@Bean
WebClient.Builder webClientBuilder(McpSyncClientExchangeFilterFunction filterFunction) {
return WebClient.builder().apply(filterFunction.configuration());
}
```
To add tokens to MCP Client request, we need a custom `ExchangeFilterFunction` that decides which OAuth2 tokens it uses, depending on the context (user interaction or app initialization).
It can look a bit confusing for Spring Security beginners, but feel free to use it as-is:
```java theme={null}
/**
* A wrapper around Spring Security's
* {@link ServletOAuth2AuthorizedClientExchangeFilterFunction}, which adds OAuth2
* {@code access_token}s to requests sent to the MCP server.
*
* The end goal is to use access_token that represent the end-user's permissions. Those
* tokens are obtained using the {@code authorization_code} OAuth2 flow, but it requires a
* user to be present and using their browser.
*
* By default, the MCP tools are initialized on app startup, so some requests to the MCP
* server happen, to establish the session (/sse), and to send the {@code initialize} and
* e.g. {@code tools/list} requests. For this to work, we need an access_token, but we
* cannot get one using the authorization_code flow (no user is present). Instead, we rely
* on the OAuth2 {@code client_credentials} flow for machine-to-machine communication.
*/
@Component
public class McpSyncClientExchangeFilterFunction implements ExchangeFilterFunction {
private final ClientCredentialsOAuth2AuthorizedClientProvider clientCredentialTokenProvider = new ClientCredentialsOAuth2AuthorizedClientProvider();
private final ServletOAuth2AuthorizedClientExchangeFilterFunction delegate;
private final ClientRegistrationRepository clientRegistrationRepository;
// Must match registration id in property
// spring.security.oauth2.client.registration..authorization-grant-type=authorization_code
private static final String AUTHORIZATION_CODE_CLIENT_REGISTRATION_ID = "authserver";
// Must match registration id in property
// spring.security.oauth2.client.registration..authorization-grant-type=client_credentials
private static final String CLIENT_CREDENTIALS_CLIENT_REGISTRATION_ID = "authserver-client-credentials";
public McpSyncClientExchangeFilterFunction(OAuth2AuthorizedClientManager clientManager,
ClientRegistrationRepository clientRegistrationRepository) {
this.delegate = new ServletOAuth2AuthorizedClientExchangeFilterFunction(clientManager);
this.delegate.setDefaultClientRegistrationId(AUTHORIZATION_CODE_CLIENT_REGISTRATION_ID);
this.clientRegistrationRepository = clientRegistrationRepository;
}
/**
* Add an {@code access_token} to the request sent to the MCP server.
*
* If we are in the context of a ServletRequest, this means a user is currently
* involved, and we should add a token on behalf of the user, using the
* {@code authorization_code} grant. This typically happens when doing an MCP
* {@code tools/call}.
*
* If we are NOT in the context of a ServletRequest, this means we are in the startup
* phases of the application, where the MCP client is initialized. We use the
* {@code client_credentials} grant in that case, and add a token on behalf of the
* application itself.
*/
@Override
public Mono filter(ClientRequest request, ExchangeFunction next) {
if (RequestContextHolder.getRequestAttributes() instanceof ServletRequestAttributes) {
return this.delegate.filter(request, next);
}
else {
var accessToken = getClientCredentialsAccessToken();
var requestWithToken = ClientRequest.from(request)
.headers(headers -> headers.setBearerAuth(accessToken))
.build();
return next.exchange(requestWithToken);
}
}
private String getClientCredentialsAccessToken() {
var clientRegistration = this.clientRegistrationRepository
.findByRegistrationId(CLIENT_CREDENTIALS_CLIENT_REGISTRATION_ID);
var authRequest = OAuth2AuthorizationContext.withClientRegistration(clientRegistration)
.principal(new AnonymousAuthenticationToken("client-credentials-client", "client-credentials-client",
AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS")))
.build();
return this.clientCredentialTokenProvider.authorize(authRequest).getAccessToken().getTokenValue();
}
/**
* Configure a {@link WebClient} to use this exchange filter function.
*/
public Consumer configuration() {
return builder -> builder.defaultRequest(this.delegate.defaultRequest()).filter(this);
}
}
```
And with that, we have everything we need! Asking our LLM weather-related questions will trigger a call our Weather MCP tool:
```java theme={null}
var chatResponse = chatClient.prompt("What is the weather in %s right now?".formatted(query))
.call()
.content();
```
If you'd like to try it for yourself, we have [a fully packaged demo application](https://github.com/Kehrlann/spring-ai-mcp-authorization-demo/) available on GitHub.
## What's next?
This is a first step implementing full, end-to-end authorization.
By using Spring's powerful extensibility, we can add OAuth2 to our MCP Clients and Servers, but it requires writing some code.
The Spring team is hard at work building a simpler integration, with the delightful configuration-driven Boot user experience.
We are also working on fine-grained permissions for MCP Servers.
In more advanced use-cases, not all tools/resources/prompts in an MCP Server will require the same permissions: the "thing-reader" tool will be available to every user, but the "thing-writer" is only available to admins.
***
\[1]: [Model Context Protocol](https://docs.spring.io/spring-ai/reference/1.0/api/mcp/mcp-overview.html), or MCP for short, is a protocol allow AI models to interact with and access external tools and resources in a structured way. Spring AI provides [out-of-the box support](https://docs.spring.io/spring-ai/reference/api/mcp/mcp-overview.html) for both MCP Servers and MCP Clients.
# Dynamic Tool Updates in Spring AI's Model Context Protocol
Source: https://springaicommunity.mintlify.app/blog/mcp/mcp-dynamic-tool-updates
The Model Context Protocol (MCP) is a powerful feature in Spring AI that enables AI models to access external tools and resources through a standardized...
The Model Context Protocol (MCP) is a powerful feature in Spring AI that enables AI models to access external tools and resources through a standardized interface. One interesting capabilities of MCP is its ability to dynamically update available tools at runtime.
This blog post explores how Spring AI implements dynamic tool updates in MCP, providing flexibility and extensibility to AI-powered applications.
The related example code is available here: [Dynamic Tool Update Example](https://github.com/spring-projects/spring-ai-examples/tree/main/model-context-protocol/dynamic-tool-update)
## Understanding the Model Context Protocol
Before diving into dynamic tool updates, let's understand what MCP is and why it matters:
[The Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) is a standardized interface that allows AI applicaitons and Agents to: **Access external tools** , **Retrieve resources** , **Use prompt templates** .
MCP follows a [client-server architecture](https://modelcontextprotocol.io/docs/concepts/architecture): **MCP Servers** - expose tools, resources, and prompts; **MCP Clients** - connect to servers and use their capabilities; **AI Models** - interact with the world through these clients
Spring AI provides a comprehensive implementation of MCP with both [client](https://docs.spring.io/spring-ai/reference/api/mcp/mcp-client-boot-starter-docs.html) and [server](https://docs.spring.io/spring-ai/reference/api/mcp/mcp-server-boot-starter-docs.html) components, making it easy to integrate AI capabilities into Spring applications.
### The Dynamic Tool Update Feature
One powerful aspects of the MCP is the ability to dynamically update the available tools at runtime. This means:
* MCP Servers can add or remove tools without restarting
* MCP Clients can detect these changes
* AI models can immediately use the new capabilities
## How Dynamic Tool Updates Work
The dynamic tool update process involves several components working together:
### Server-Side Implementation
Spring AI's `@Tool` annotation makes it easy to expose methods as MCP tools:
```java theme={null}
public class MathTools {
@Tool(description = "Adds two numbers")
public int sumNumbers(int number1, int number2) {
return number1 + number2;
}
// ...
}
```
The framework automatically:
1. Extracts method parameters as tool inputs
2. Generates appropriate JSON schemas
3. Handles parameter validation and conversion
On the server side, Spring AI's MCP implementation provides a straightforward way to add MCP tools at start time and dynamically at runtime:
```java theme={null}
@SpringBootApplication
public class ServerApplication {
//1. Tools added at start time by the Spring AI MCP Server Boot starter
@Bean
public ToolCallbackProvider weatherTools(WeatherService weatherService) {
return MethodToolCallbackProvider.builder().toolObjects(weatherService).build();
}
//2. Runtime tool addition
@Bean
public CommandLineRunner commandRunner(McpSyncServer mcpSyncServer) {
return args -> {
// Wait for some tool update signal
// Add math tools dynamically
List newTools = McpToolUtils
.toSyncToolSpecifications(ToolCallbacks.from(new MathTools()));
for (SyncToolSpecification newTool : newTools) {
mcpSyncServer.addTool(newTool);
}
};
}
}
```
In this example:
1. The server initially exposes only weather forecast tools
2. When a custom tool-update signal is received the `McpSyncServer.addTool()` method is used to dynamically register new tools
The `McpSyncServer` class provides methods for tool management:
* `addTool(SyncToolSpecification)` - Adds a new tool
* `removeTool(String)` - Removes a tool by name
* `notifyToolsListChanged()` - Notifies clients about tool changes
> **NOTE:** you can add and/or remove Tools only after the Clinet/Server connection has been initialized.
### Client-Side Implementation
The MCP protocol includes a notification system that allows servers to inform clients about changes to available tools. This notification system ensures that clients always have an up-to-date view of the server's capabilities.
On the client side, Spring AI provides mechanisms to detect and react to tool changes:
```java theme={null}
@Bean
McpSyncClientCustomizer customizeMcpClient() {
return (name, mcpClientSpec) -> {
mcpClientSpec.toolsChangeConsumer(tv -> {
logger.info("\nMCP TOOLS CHANGE: " + tv);
latch.countDown();
});
};
}
```
The client registers a listener that is invoked whenever the server's available tools change. This allows the client to:
1. Be notified when tools are added or removed
2. Update its internal state accordingly
3. Make the new tools immediately available to the AI model
Currently Spring AI doesn't maintain internal state about the updated tools, but you can use the customization listener to implement smart tool caching or similar.
### Tool Discovery and Usage
The client can discover available tools at any time:
```java theme={null}
List toolDescriptions = chatClientBuilder.build()
.prompt("What tools are available?")
.toolCallbacks(tools)
.call()
.entity(new ParameterizedTypeReference>() {});
```
A key insight from the Spring AI MCP implementation is:
> **TIP**: The client implementation relies on the fact that the [ToolCallbackProvider#getToolCallbacks](https://github.com/spring-projects/spring-ai/blob/9e71b163e315199fe7b46495d87a0828a807b88f/mcp/common/src/main/java/org/springframework/ai/mcp/SyncMcpToolCallbackProvider.java#L132) implementation for MCP will always retrieves the current list of MCP tools from the server.
> This means that whenever a client requests the available tools, it will always get the most up-to-date list from the server, without needing to restart or reinitialize the client.
## Practical Applications
Dynamic tool updates in MCP enable several powerful use cases:
### 1. Feature Flags for AI Capabilities
You can implement feature flags that control which AI capabilities are available:
```java theme={null}
if (featureFlags.isEnabled("advanced-math")) {
mcpSyncServer.addTool(advancedMathTools);
}
```
### 2. Context-Aware Tool Loading
Load tools based on the current context or user permissions:
```java theme={null}
if (userHasPermission(currentUser, "admin-tools")) {
mcpSyncServer.addTool(adminTools);
}
```
### 3. Progressive Enhancement
Start with basic tools and add more advanced capabilities as needed:
```java theme={null}
// Start with basic tools
mcpSyncServer.addTool(basicTools);
// Add advanced tools when the user reaches a certain level
userService.onUserLevelUp(user -> {
if (user.getLevel() >= 5) {
mcpSyncServer.addTool(advancedTools);
}
});
```
### 4. Dynamic Plugin Architecture
Implement a plugin system where new capabilities can be added at runtime:
```java theme={null}
pluginRegistry.onPluginLoaded(plugin -> {
if (plugin.hasMcpTools()) {
mcpSyncServer.addTool(plugin.getMcpTools());
}
});
```
## Conclusion
Spring AI's handling of dynamic tool updates in the Model Context Protocol provides a mechanism for extending AI capabilities at runtime. This feature enables more flexible, extensible, and resource-efficient AI applications.
Key takeaways:
1. **Standardized Interface**: MCP provides a consistent way for AI models to interact with external tools and resources.
2. **Dynamic Updates**: Tools can be added or removed at runtime without requiring application restarts.
3. **Automatic Discovery**: Clients can detect changes to available tools and make them immediately available to AI models.
4. **Simple API**: Spring AI provides a clean, annotation-based API for defining and managing MCP tools.
By leveraging dynamic tool updates in Spring AI's MCP implementation, developers can create more adaptable AI applications that can evolve their capabilities based on runtime conditions, user needs, and system requirements.
## Resources
* [Spring AI Documentation](https://docs.spring.io/spring-ai/reference/)
* [Model Context Protocol Tools Specification](http://localhost:3000/specification/2024-11-05/server/tools)
* [Spring AI Examples Repository](https://github.com/spring-projects/spring-ai-examples)
# Connect Your AI to Everything: Spring AI's MCP Boot Starters
Source: https://springaicommunity.mintlify.app/blog/mcp/mcp-intro
The Model Context Protocol (MCP) standardizes how AI applications interact with external tools and resources.
The Model Context Protocol (MCP) standardizes how AI applications interact with external tools and resources.
Spring joined the MCP ecosystem early as a key contributor, helping to develop and maintain the [official MCP Java SDK](https://modelcontextprotocol.io/sdk/java/mcp-overview) that serves as the foundation for Java-based MCP implementations.
Building on this contribution, Spring AI has embraced MCP with comprehensive support through dedicated [Boot Starters](https://docs.spring.io/spring-ai/reference/1.1-SNAPSHOT/api/mcp/mcp-overview.html#_spring_ai_mcp_integration) and [MCP Java Annotations](https://docs.spring.io/spring-ai/reference/1.1-SNAPSHOT/api/mcp/mcp-annotations-overview.html), making it easier than ever to build sophisticated AI-powered applications that can seamlessly connect to external systems.
This blog introduces core MCP components and demonstrates building both MCP Servers and Clients using Spring AI, showcasing basic and advanced features. The complete source code is available at: [MCP Weather Example](https://github.com/tzolov/spring-ai-mcp-blogpost).
> **Note:** This content applies only to Spring AI `1.1.0-SNAPSHOT` or Spring `AI 1.1.0-M1+` versions.
## What is the Model Context Protocol?
The [Model Context Protocol (MCP)](https://modelcontextprotocol.org/docs/concepts/architecture) is a standardized protocol that enables AI models to interact with external tools and resources in a structured way.
Think of it as a bridge between your AI models and the real world - allowing them to access databases, APIs, file systems, and other external services through a consistent interface.
You can bootstrap AI applications with MCP support using [Spring Initializr](https://start.spring.io).
For comprehensive details, see the [Spring AI MCP Overview](https://docs.spring.io/spring-ai/reference/1.1-SNAPSHOT/api/mcp/mcp-overview.html) documentation.
### MCP Client-Server Architecture
The Model Context Protocol follows a Client-Server architecture that ensures a clear separation of concerns.
The MCP Server exposes specific capabilities (tools, resources, prompts) from third-party services.
MCP clients are instantiated by host applications to communicate with particular MCP servers. Each client handles one direct communication with one server.
The Host is the AI application users interact with, while clients are the protocol-level components that enable server connections.
The MCP protocol ensures complete, language-agnostic interoperability between clients and servers.
You can have Clients written in Java, Python, or TypeScript communicating with servers in any language and vice versa.
This architecture establishes distinct boundaries and responsibilities between client and server-side development, naturally creating two distinct developer communities:
**AI Application/Host Developers**
Handle the complexity of orchestrating multiple MCP servers (connected via MCP Clients) and integrating with AI models. AI developers build AI applications that:
* Use MCP Clients to consume capabilities from multiple MCP Servers
* Handle AI model integration and prompt engineering
* Manage conversation context and user interactions
* Orchestrate complex workflows across different services
* Focus on creating compelling user experiences
**MCP Server (Provider) Developers**
Focus on exposing specific capabilities (tools, resources, prompts) from third-party services as MCP Servers. Server developers create servers that:
* Wrap third-party services and APIs (databases, file systems, external APIs)
* Expose service capabilities through standardized MCP primitives (tools, resources, prompts)
* Handle authentication and authorization for their specific services
Such separation ensures that Server developers can concentrate on wrapping their domain-specific services without worrying about AI orchestration. At the same time the AI application developers can leverage existing MCP servers without understanding the intricacies of each third-party service.
The division of labor means that a database expert can create an MCP server for PostgreSQL without needing to understand LLM prompting, while an AI application developer can use that PostgreSQL server without knowing SQL internals. The MCP protocol acts as the universal language between them.
**Spring AI** embraces this architecture with [MCP Client](https://docs.spring.io/spring-ai/reference/1.1-SNAPSHOT/api/mcp/mcp-client-boot-starter-docs.html) and [MCP Server](https://docs.spring.io/spring-ai/reference/1.1-SNAPSHOT/api/mcp/mcp-server-boot-starter-docs.html) Boot Starters.
This means Spring developers can participate in both sides of the MCP ecosystem - building AI applications that consume MCP servers and creating MCP servers that expose Spring-based services to the wider AI community.
### MCP Features
Shared between the Client and the Server, MCP provides an extensive set of features that enable seamless communication between AI applications and external services:
* Expose [tools](https://modelcontextprotocol.io/specification/2025-06-18/server/tools) that AI models can invoke
* Share [resources](https://modelcontextprotocol.io/specification/2025-06-18/server/resources) and data with AI applications
* Provide [prompt](https://modelcontextprotocol.io/specification/2025-06-18/server/prompts) templates for consistent interactions
* Offers argument [autocompletion](https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/completion) suggestions for prompts and resource URIs
* Handle real-time notifications and [progress](https://modelcontextprotocol.io/specification/2025-06-18/basic/utilities/progress) updates
* Support client-side [sampling](https://modelcontextprotocol.io/specification/2025-06-18/client/sampling), [elicitation](https://modelcontextprotocol.io/specification/2025-06-18/client/elicitation), [structured logging](https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/logging) and [progress tracking](https://modelcontextprotocol.io/specification/2025-06-18/basic/utilities/progress)
* Support various transport protocols: [STDIO](http://localhost:3000/specification/2025-06-18/basic/transports#stdio), [Streamable-HTTP](http://localhost:3000/specification/2025-06-18/basic/transports#streamable-http), and [SSE](http://localhost:3000/specification/2024-11-05/basic/transports#http-with-sse)
> **Important:** Tools are owned by the LLM, unlike other MCP features such as prompts and resources. The LLM—not the Host—decides if, when, and in what order to call tools. The Host only controls which tool descriptions are offered to the LLM.
#### Client Features
MCP Clients enable AI applications to consume capabilities from MCP Servers:
* **Roots**: Expose filesystem "roots" to servers
* **Sampling**: Standardized way for servers to request LLM sampling from LLMS via clients
* **Elicitation**: Standardized way for servers to request additional information from users through the client during interactions.
* **Progress Tracking Listener**: Monitor long-running operations with real-time progress updates
* **Structured Logging Listener**: Receive detailed log messages from servers for debugging and monitoring
* **Change Notifications Listeners**: Get notified when server capabilities (tools, resources, prompts) are updated
#### Server Features
MCP Servers expose capabilities and services to AI applications:
* **Tools**: Publish functions that AI models can invoke through standardized interfaces
* **Resources**: Provide access to data sources, files, and external systems
* **Prompts**: Share reusable prompt templates with parameter support
* **Completion**: Argument autocompletion suggestions for prompts and resource URIs
* **Real-time Notifications**: Send updates about capability changes to connected clients
* **Progress Reporting**: Emit progress updates for long-running operations
* **Structured Logging**: Send detailed log messages to clients for transparency
## Build an MCP Server
Let's build a [Streamable-HTTP MCP Server](https://docs.spring.io/spring-ai/reference/1.1-SNAPSHOT/api/mcp/mcp-streamable-http-server-boot-starter-docs.html) that provides real-time weather forecast information.
#### Spring Boot Server application
Create a new (`mcp-weather-server`) Spring Boot application:
```java theme={null}
@SpringBootApplication
public class McpServerApplication {
public static void main(String[] args) {
SpringApplication.run(McpServerApplication.class, args);
}
}
```
with Spring AI MCP Server dependency:
```xml theme={null}
org.springframework.ai
spring-ai-starter-mcp-server-webmvc
```
Find more about the available server [dependency options](https://docs.spring.io/spring-ai/reference/1.1-SNAPSHOT/api/mcp/mcp-server-boot-starter-docs.html#_mcp_server_boot_starters).
In `application.properties` to enable the [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports#streamable-http) server transport:
```bash theme={null}
spring.ai.mcp.server.protocol=STREAMABLE
```
You can start the server with either `STREAMABLE`, `STATELESS` or `SSE` transport.
To enable the `STDIO` transport you need to set `spring.ai.mcp.server.stdio=true`.
#### Weather Service
Leverage the free [Weather REST API](https://open-meteo.com/) to build a service that can retrieve weather forecasts by location coordinates.
Add @McpTool and @McpToolParam annotations to register the `getTemperature` method as an MCP Server Tool:
```java theme={null}
@Service
public class WeatherService {
public record WeatherResponse(Current current) {
public record Current(LocalDateTime time, int interval, double temperature_2m) {}
}
@McpTool(description = "Get the temperature (in celsius) for a specific location")
public WeatherResponse getTemperature(
@McpToolParam(description = "The location latitude") double latitude,
@McpToolParam(description = "The location longitude") double longitude) {
return RestClient.create()
.get()
.uri("https://api.open-meteo.com/v1/forecast?latitude={latitude}&longitude={longitude}¤t=temperature_2m",
latitude, longitude)
.retrieve()
.body(WeatherResponse.class);
}
}
```
#### Build & Run
```bash theme={null}
./mvnw clean install -DskipTests
java -jar target/mcp-weather-server-0.0.1-SNAPSHOT.jar
```
This starts the mcp-weather-server on port `8080`.
#### Using the MCP Server
Once the MCP Weather Server is up and running, you can interact with it using various MCP compliant client applications:
**[MCP Inspector](https://modelcontextprotocol.io/legacy/tools/inspector)**
The MCP Inspector is an interactive developer tool for testing and debugging MCP servers.
To start the inspector run:
```bash theme={null}
npx @modelcontextprotocol/inspector
```
In the browser UI, set the Transport Type to `Streamable HTTP` and the URL to `http://localhost:8080/mcp`.
Click `Connect` to establish the connection.
Then list the tools and run the getTemperature.
**[MCP Java SDK](https://modelcontextprotocol.io/sdk/java/mcp-client#client-features)**
Use the MCP Java SDK client to programmatically connect to the server:
```java theme={null}
var client = McpClient.sync(
HttpClientStreamableHttpTransport
.builder("http://localhost:8080").build())
.build();
client.initialize();
CallToolResult weather = client.callTool(
new CallToolRequest("getTemperature",
Map.of("latitude", "47.6062",
"longitude", "-122.3321")));
```
**Other MCP compliant AI Applications/SDKs**
Connect your MCP server to popular AI applications:
* [Cline](https://docs.cline.bot/mcp/mcp-overview) - AI coding assistant for VS Code
* [VS Code MCP](https://code.visualstudio.com/docs/copilot/customization/mcp-servers) - GitHub Copilot MCP integration
* [Cursor MCP](https://docs.cursor.com/en/context/mcp)
* [Non-Java MCP client](https://modelcontextprotocol.io/docs/develop/build-client) - Build MCP Client using with other (non-Java) SDKs
* ...
**[Claude Desktop](https://claude.ai/download)**
To integrate with Claude Desktop, using the local [STDIO transport](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports#stdio), add the following configuration to your Claude Desktop settings:
```json theme={null}
{
"mcpServers": {
"spring-ai-mcp-weather": {
"command": "java",
"args": [
"-Dspring.ai.mcp.server.stdio=true",
"-Dspring.main.web-application-type=none",
"-Dlogging.pattern.console=",
"-jar",
"/path/to/mcp-weather-server-0.0.1.jar"]
}
}
}
```
*Replace `/absolute/path/to/` with the actual path to your built JAR file.*
Follow the [MCP server installation for Claude Desktop](https://www.anthropic.com/engineering/desktop-extensions) for further guidance. The free version of the Claude Desktop doesn't support Sampling!
### Advanced Server Features
Let's extend our MCP Weather Server to demonstrate advanced MCP capabilities including Logging, Progress Tracking, and Sampling.
These features enable rich interactions between servers and clients:
* **Logging**: Send structured log messages to connected clients for debugging and monitoring
* **Progress Tracking**: Report real-time progress updates for long-running operations
* **Sampling**: Request the client's LLM to generate content based on server data
In this enhanced version, our weather server will log its operations to the client for transparency, report progress as it fetches and processes weather data
and request the client's LLM to generate an epic poem about the weather forecast
Here's the updated server implementation:
```java theme={null}
@Service
public class WeatherService {
public record WeatherResponse(Current current) {
public record Current(LocalDateTime time, int interval, double temperature_2m) {}
}
@McpTool(description = "Get the temperature (in celsius) for a specific location")
public String getTemperature(
McpSyncServerExchange exchange, // (1)
@McpToolParam(description = "The location latitude") double latitude,
@McpToolParam(description = "The location longitude") double longitude,
@McpProgressToken String progressToken) { // (2)
exchange.loggingNotification(LoggingMessageNotification.builder() // (3)
.level(LoggingLevel.DEBUG)
.data("Call getTemperature Tool with latitude: " + latitude + " and longitude: " + longitude)
.meta(Map.of()) // non null meta as a workaround for bug: ...
.build());
WeatherResponse weatherResponse = RestClient.create()
.get()
.uri("https://api.open-meteo.com/v1/forecast?latitude={latitude}&longitude={longitude}¤t=temperature_2m",
latitude, longitude)
.retrieve()
.body(WeatherResponse.class);
String epicPoem = "MCP Client doesn't provide sampling capability.";
if (exchange.getClientCapabilities().sampling() != null) {
// 50% progress
exchange.progressNotification(new ProgressNotification(progressToken, 0.5, 1.0, "Start sampling")); // (4)
String samplingMessage = """
For a weather forecast (temperature is in Celsius): %s.
At location with latitude: %s and longitude: %s.
Please write an epic poem about this forecast using a Shakespearean style.
""".formatted(weatherResponse.current().temperature_2m(), latitude, longitude);
CreateMessageResult samplingResponse = exchange.createMessage(CreateMessageRequest.builder()
.systemPrompt("You are a poet!")
.messages(List.of(new SamplingMessage(Role.USER, new TextContent(samplingMessage))))
.build()); // (5)
epicPoem = ((TextContent) samplingResponse.content()).text();
}
// 100% progress
exchange.progressNotification(new ProgressNotification(progressToken, 1.0, 1.0, "Task completed"));
return """
Weather Poem: %s
about the weather: %s°C at location: (%s, %s)
""".formatted(epicPoem, weatherResponse.current().temperature_2m(), latitude, longitude);
}
}
```
1. **McpSyncServerExchange** - the `exchange` parameter provides access to server-client communication capabilities. It allows the server to send notifications and make requests back to the client.
2. **@ProgressToken** - the `progressToken` parameter enables progress tracking. The client provides this token, and the server uses it to send progress updates.
3. **Logging Notifications** - sends structured log messages to the client for debugging and monitoring purposes.
4. **Progress Updates** - reports operation progress (50% in this case) to the client with a descriptive message.
5. **Sampling Capability** - the most powerful feature - the server can request the client's LLM to generate content.
This allows the server to leverage the client's AI capabilities, creating a bidirectional AI interaction pattern.
The enhanced weather service now returns not just weather data, but a creative poem about the forecast, demonstrating the powerful synergy between MCP servers and AI models.
## Build an MCP Client
Let's build an AI application that uses an LLM and connects to MCP Servers via MCP Clients.
#### Client Configuration
Create a new Spring Boot project (`mcp-weather-client`) with the following dependencies:
```xml theme={null}
org.springframework.ai
spring-ai-starter-mcp-client
org.springframework.ai
spring-ai-starter-model-anthropic
```
*Find about the [available dependency options](https://docs.spring.io/spring-ai/reference/1.1-SNAPSHOT/api/mcp/mcp-client-boot-starter-docs.html#_starters) to configure different transport mechanisms.*
In `application.yml`, configure the connection to the MCP Server:
```yml theme={null}
spring:
main:
web-application-type: none
ai:
# Set credentials for your Anthropic API account
anthropic:
api-key: ${ANTHROPIC_API_KEY}
# Connect to the MCP Weather Server using streamable-http client transport
mcp:
client:
streamable-http:
connections:
my-weather-server:
url: http://localhost:8080
```
Note that the configuration has assigned the `my-weather-server` name to the server connection.
#### Spring Boot Client Application
Create a client application that uses `ChatClient` connected to an LLM and to the MCP Weather Server:
```java theme={null}
@SpringBootApplication
public class McpClientApplication {
public static void main(String[] args) {
SpringApplication.run(McpClientApplication.class, args).close(); // (1)
}
@Bean
public ChatClient chatClient(ChatClient.Builder chatClientBuilder) { // (2)
return chatClientBuilder.build();
}
String userPrompt = """
Check the weather in Amsterdam right now and show the creative response!
Please incorporate all creative responses from all LLM providers.
""";
@Bean
public CommandLineRunner predefinedQuestions(ChatClient chatClient, ToolCallbackProvider mcpToolProvider) { // (3)
return args -> System.out.println(
chatClient.prompt(userPrompt) // (4)
.toolContext(Map.of("progressToken", "token-" + new Random().nextInt())) // (5)
.toolCallbacks(mcpToolProvider) // (6)
.call()
.content());
}
}
```
1. **Application Lifecycle Management** - the application starts, executes the weather query, displays the result, and then exits cleanly.
2. **ChatClient Configuration** - creates a configured [ChatClient](https://docs.spring.io/spring-ai/reference/1.1-SNAPSHOT/api/chatclient.html) bean using Spring AI's auto-configured builder. The builder is automatically populated with:
* The AI model configuration (Anthropic Claude in our case)
* Default settings and configurations from application.properties
3. **CommandLineRunner** - runs automatically after the application context is fully loaded. It injects the configured ChatClient for AI model interaction and the `ToolCallbackProvider` which contains all registered MCP tools from connected servers.
4. **AI Prompt** - instructs the AI model to get Amsterdam's current weather. The AI model automatically discovers and calls the appropriate MCP tools based on the prompt.
5. **Progress Token** - uses the `toolContext` to pass a unique `progressToken` to MCP tools annotated with @McpProgressToken parameter.
6. **MCP Tool Integration** - this crucial line connects the ChatClient to all available MCP tools:
* `mcpToolProvider` is auto-configured by Spring AI's MCP Client starter
* Contains all tools from connected MCP servers (configured via `spring.ai.mcp.client.*.connections.*`)
* The AI model can automatically discover and invoke these tools during conversation
#### Client MCP Handlers
Create a service class to handle MCP notifications and requests from the server.
These handlers are the **client-side counterparts** to the advanced server features we implemented above, enabling bidirectional communication between the MCP Server and Client:
```java theme={null}
@Service
public class McpClientHandlers {
private static final Logger logger = LoggerFactory.getLogger(McpClientHandlers.class);
private final ChatClient chatClient;
public McpClientHandlers(@Lazy ChatClient chatClient) { // Lazy is needed to avoid circular dependency
this.chatClient = chatClient;
}
@McpProgress(clients = "my-weather-server") // (1)
public void progressHandler(ProgressNotification progressNotification) {
logger.info("MCP PROGRESS: [{}] progress: {} total: {} message: {}",
progressNotification.progressToken(), progressNotification.progress(),
progressNotification.total(), progressNotification.message());
}
@McpLogging(clients = "my-weather-server")
public void loggingHandler(LoggingMessageNotification loggingMessage) {
logger.info("MCP LOGGING: [{}] {}", loggingMessage.level(), loggingMessage.data());
}
@McpSampling(clients = "my-weather-server")
public CreateMessageResult samplingHandler(CreateMessageRequest llmRequest) {
logger.info("MCP SAMPLING: {}", llmRequest);
String llmResponse = chatClient
.prompt()
.system(llmRequest.systemPrompt())
.user(((TextContent) llmRequest.messages().get(0).content()).text())
.call()
.content();
return CreateMessageResult.builder().content(new TextContent(llmResponse)).build();
}
}
```
##### Understanding the Handler Components:
1. **Progress Handler** - Receives real-time progress updates from the server's long-running operations. Triggered when the server calls `exchange.progressNotification(...)`. For example the weather server sends 50% progress when starting sampling, then 100% when complete. Commonly used to display progress bars, update UI status, or log operation progress.
2. **Logging Handler** - Receives structured log messages from the server for debugging and monitoring. Triggered when the server calls `exchange.loggingNotification(...)`. For example the weather server logs "Call getTemperature Tool with latitude: X and longitude: Y". Used to debug server operations, audit trails, or monitoring dashboards.
3. **Sampling Handler** - The Most Powerful Feature. It enables the server to request AI-generated content from the client's LLM. Used for bidirectional AI interactions, creative content generation, dynamic responses. Triggered when the server calls `exchange.createMessage(...)` with sampling capability check. The execution flow looks like this:
* If client supports sampling, requests a poem about the weather
* Client handler receives the request and uses its ChatClient to interact with the LLM and generate the poem
* Generated poem is returned to the server and incorporated into the final tool response
##### Key Design Patterns:
* **Annotation-Based Routing**: The `clients = "my-weather-server"` attribute ensures handlers only process notifications from the specific MCP server connection defined in your configuration: `spring.ai.mcp.client.streamable-http.connections.[my-weather-server].url`.
If your application connects to multiple MCP servers, use the `clients` attribute to assign each handler to the corresponding MCP Client:
```java theme={null}
@McpProgress(clients = {"weather-server", "database-server"}) // Handle progress from multiple servers
public void multiServerProgressHandler(ProgressNotification notification) {
// Handle progress from both servers
}
@McpSampling(clients = "specialized-ai-server") // Handle sampling from specific server
public CreateMessageResult specializedSamplingHandler(CreateMessageRequest request) {
// Handle sampling requests from specialized AI server
}
```
* The **@Lazy** annotation on ChatClient prevents circular dependency issues that can occur when the ChatClient also depends on MCP components
* **Bidirectional AI Communication**: The sampling handler creates a powerful pattern where:
* The server (domain expert) can leverage the client's AI capabilities
* The client's LLM generates creative content based on server-provided context
* This enables sophisticated AI-to-AI interactions beyond simple tool invocation
This architecture makes the MCP Client a **reactive participant** in server operations, enabling sophisticated interactions rather than just passive tool consumption.
#### Multiple MCP Servers
Connect to multiple MCP servers using different transports.
Here's how to add the [Brave Search MCP Server](https://github.com/brave/brave-search-mcp-server) for web search alongside your weather server:
```yaml theme={null}
spring:
ai:
anthropic:
api-key: ${ANTHROPIC_API_KEY}
mcp:
client:
streamable-http:
connections:
my-weather-server:
url: http://localhost:8080
stdio:
connections:
brave-search:
command: npx
args: ["-y",
"@modelcontextprotocol/server-brave-search"]
```
It uses the STDIO client transport.
Now your LLM can combine weather data and web search in a single prompt:
```java theme={null}
String userPrompt = """
Check the weather in Amsterdam and show the creative response!
Please incorporate all creative responses.
Then search online to find publishers for poetry and list top 3.
""";
```
#### Build & Run
Make sure that your MCP Weather Server is up and running.
Then build and start your client:
```bash theme={null}
./mvnw clean install -DskipTests
java -jar target/mcp-weather-client-0.0.1-SNAPSHOT.jar
```
## Conclusion
The combination of Spring's proven development model with MCP's standardized protocol creates a powerful foundation for the next generation of AI applications.
Whether you're building chatbots, data analysis tools, or development assistants, Spring AI's MCP support provides the building blocks you need.
This introduction covered the essential MCP concepts and demonstrated how to build both MCP Servers and Clients using Spring AI's Boot Starters with basic Tool functionality. However, the MCP ecosystem offers much more sophisticated capabilities that we'll explore in upcoming blog posts:
* **Java MCP Annotations Deep Dive**: Learn how to leverage Spring AI's annotation-based approach for creating more maintainable and declarative MCP implementations, including advanced annotation patterns and best practices.
* **Beyond Tools - Prompts, Resources & Completions**: Discover how to implement the full spectrum of MCP capabilities including shared prompt templates, dynamic resource provisioning, and intelligent autocompletion features that make your MCP servers more user-friendly and powerful.
* **Authorization support - securing MCP Servers**: Secure your MCP Servers with OAuth 2, and ensure only authorized users can access tools, resources and other capabilities. Add authorization support to your MCP Clients, so they can obtain OAuth 2 tokens to authenticate with secure MCP servers.
Ready to get started? Check out the [example applications](https://github.com/spring-projects/spring-ai-examples/tree/main/model-context-protocol) and explore the full potential of AI integration with Spring AI and MCP.
## Additional Resources
* [Model Context Protocol Specification](https://modelcontextprotocol.io/specification/) - Official MCP protocol documentation
* [MCP Java SDK](https://modelcontextprotocol.io/sdk/java/mcp-overview) - MCP Java SDK documentation
* [MCP Weather Example Code](https://github.com/tzolov/spring-ai-mcp-blogpost)
* [Spring AI MCP Overview](https://docs.spring.io/spring-ai/reference/1.1-SNAPSHOT/api/mcp/mcp-overview.html) - Complete architectural overview and concepts
* [MCP Client Boot Starter](https://docs.spring.io/spring-ai/reference/1.1-SNAPSHOT/api/mcp/mcp-client-boot-starter-docs.html) - Client configuration and usage guide
* [MCP Server Boot Starter](https://docs.spring.io/spring-ai/reference/1.1-SNAPSHOT/api/mcp/mcp-server-boot-starter-docs.html) - Server setup and configuration
* [STDIO and SSE Servers](https://docs.spring.io/spring-ai/reference/1.1-SNAPSHOT/api/mcp/mcp-stdio-sse-server-boot-starter-docs.html) - Traditional transport mechanisms
* [Streamable-HTTP Servers](https://docs.spring.io/spring-ai/reference/1.1-SNAPSHOT/api/mcp/mcp-streamable-http-server-boot-starter-docs.html) - Modern HTTP-based transport
* [Stateless Streamable-HTTP Servers](https://docs.spring.io/spring-ai/reference/1.1-SNAPSHOT/api/mcp/mcp-stateless-server-boot-starter-docs.html) - Cloud-native deployment options
* [Spring AI MCP Java Annotations](https://docs.spring.io/spring-ai/reference/1.1-SNAPSHOT/api/mcp/mcp-annotations-overview.html) - Annotation-based method handling for MCP servers and clients in Java
* [Client Annotations](https://docs.spring.io/spring-ai/reference/1.1-SNAPSHOT/api/mcp/mcp-annotations-client.html) - Declarative way to implement MCP client handlers using Java annotations
* [Server Annotations](https://docs.spring.io/spring-ai/reference/1.1-SNAPSHOT/api/mcp/mcp-annotations-server.html) - Declarative way to implement MCP server functionality using Java annotations
* [Special Parameters](https://docs.spring.io/spring-ai/reference/1.1-SNAPSHOT/api/mcp/mcp-annotations-special-params.html) - Special parameter types that provide additional context and functionality to annotated methods
***
*For the latest updates and comprehensive documentation, visit the [Spring AI Reference Documentation](https://docs.spring.io/spring-ai/reference/).*
# Introducing the Model Context Protocol Java SDK
Source: https://springaicommunity.mintlify.app/blog/mcp/mcp-java-sdk-released
> This blog post is co-authored with [David Soria Parra](https://github.com/dsp-ant), [Christian Tzolov](https://github.com/tzolov), and [Dariusz...
> This blog post is co-authored with [David Soria Parra](https://github.com/dsp-ant), [Christian Tzolov](https://github.com/tzolov), and [Dariusz Jędrzejczyk](https://github.com/chemicL).
# What is MCP
The Model Context Protocol (MCP), an open protocol developed by [Anthropic](https://www.anthropic.com/), is transforming the way AI applications connect and share context. It has garnered extensive support across AI applications, functioning as a standardized interface for Large Language Models (LLMs) to interact with data sources, tools, and AI agents. Whether you're building autonomous systems that need to access databases, orchestrating complex AI workflows, or creating systems where multiple agents collaborate, MCP provides the foundational layer that makes these integrations seamless.
What sets MCP apart is its focus on composability and interoperability. Beyond just connecting to data sources, MCP enables developers to build rich, interactive AI systems where agents can share context, access tools, and work together through a consistent interface. This means you can quickly plug into a growing ecosystem of pre-built integrations while maintaining the flexibility to switch between different LLM providers, making it an ideal foundation for building sophisticated AI applications.
# Introducing the MCP Java SDK
What began as an experimental project last November has turned into an exciting collaboration with the Spring AI team and Anthropic.
We're thrilled to announce that the experimental project has been moved into the official MCP Java SDK.\
This SDK is the latest language binding of the protocol, alongside the Python, TypeScript, and Kotlin SDKs, on [modelcontextprotocol.io](https://modelcontextprotocol.io). Java has long been the language of the enterprise, and the MCP Java SDK makes it easier for organizations to develop cutting-edge AI applications.
The MCP Java SDK provides a comprehensive foundation for integrating AI models with external tools and data sources. Key features of the SDK include:
### Client and Server Implementations
* Supports both synchronous and asynchronous MCP communication.
* Enables protocol version compatibility negotiation for smooth interoperability.
### Tool and Resource Management
* Discover, register, and execute tools dynamically.
* Receive real-time list change notifications for tools and resources.
* Manage resources using URI templates for structured access and subscriptions.
### Prompt Handling and AI Sampling Support
* Retrieve and manage prompts to customize AI model behavior.
* Supports sampling strategies to fine-tune AI interactions.
### Multiple Transport Implementations
* Stdio-based transport for direct process communication.
* Java HttpClient-based SSE client transport for HTTP-based streaming.
* Servlet-based SSE server transport for streaming over HTTP in a traditional server environment.
* Spring-based transports for seamless Spring Boot integration:
* Spring WebFlux-based SSE transport for reactive applications.
* Spring WebMVC-based SSE transport for servlet-based applications.
Please check out the [documentation](https://modelcontextprotocol.io/sdk/java/mcp-overview) for more information on getting started, and visit the [GitHub repository](https://github.com/modelcontextprotocol/java-sdk) to open issues and join discussions.
# Spring AI and MCP
The Spring AI project extends the MCP Java SDK by adding developer productivity enhancements for integration with Spring Boot applications. With Spring Boot starters, developers can quickly configure MCP clients and servers using Spring’s dependency injection and configuration management, making it easier to integrate AI-driven workflows into their applications.
#### Client Starters
* `spring-ai-mcp-client-spring-boot-starter` – Core client starter supporting STDIO and HTTP-based SSE transport.
* `spring-ai-mcp-client-webflux-spring-boot-starter` – WebFlux-based SSE transport implementation for reactive applications.
#### Server Starters
* `spring-ai-mcp-server-spring-boot-starter` – Core server starter supporting STDIO transport.
* `spring-ai-mcp-server-webmvc-spring-boot-starter` – Spring MVC-based SSE transport implementation for servlet-based applications.
* `spring-ai-mcp-server-webflux-spring-boot-starter` – WebFlux-based SSE transport implementation for reactive applications.
Here’s an example of how to declaratively configure an STDIO-transported client application. In `application.yml`, define the following configuration:
```yaml theme={null}
spring:
ai:
mcp:
client:
stdio:
servers-configuration: classpath:mcp-servers.json
```
And the referenced JSON file defines the server to connect to in the Claude Desktop format.
```json theme={null}
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-filesystem",
"/Users/username/Desktop",
"/Users/username/Downloads"
]
}
}
}
```
When the client application starts, it will launch the MCP server, establish STDIO communication channels, and manage the server lifecycle.
Spring AI M6 also introduces the `@Tool` annotation, which simplifies the creation of MCP servers. For more information, please read the [Spring AI Reference documentation on MCP.](https://docs.spring.io/spring-ai/reference/api/mcp/mcp-overview.html)
# Next Steps
We look forward to feedback on GitHub and are very grateful for the support of the Anthropic team.
# Securing Spring AI MCP servers with OAuth2
Source: https://springaicommunity.mintlify.app/blog/mcp/mcp-server-oauth2
Spring AI offers [support for Model Context Protocol](https://docs.spring.io/spring-ai/reference/1.0/api/mcp/mcp-overview.html), or MCP for short, which allows...
Spring AI offers [support for Model Context Protocol](https://docs.spring.io/spring-ai/reference/1.0/api/mcp/mcp-overview.html), or MCP for short, which allows AI models to interact with and access external tools and resources in a structured way.
With Spring AI, developers can create their own MCP Servers and expose capabilities to AI models in just a few lines of code.
## Authorization and security in MCP
MCP Servers can run locally, using the STDIO transport.
To expose an MCP server to the outside world, it must expose a few standard HTTP endpoints.
While MCP Servers used privately might not require strict authentication, enterprise deployments need robust security and permission management for exposed endpoints.
This challenge is addressed in the [newest version of the MCP specification (2025-03-26)](https://spec.modelcontextprotocol.io/specification/2025-03-26/), which was released last week.
It lays the foundation for securing communications between Clients and Servers, leveraging the widespread [OAuth2 framework](https://oauth.net/2/).
While we won't do a full review of OAuth2 in this blog post, a quick refresher might prove useful.
In the draft of the spec, the MCP Server is both a Resource Server and an Authorization Server.
As a Resource Server, it performs authorization checks on incoming requests by checking the `Authorization` header.
The header MUST contain an OAuth2 `access_token`, which is a string representing the "permissions" of the Client.
That token may be a JSON Web Token (JWT) or an opaque string that does not carry information by itself.
If the token is missing or invalid (malformed, expired, wrong recipient, ...), the Resource Server rejects the request.
Using those tokens, a typical request might look like:
```shell theme={null}
curl https://mcp.example.com/sse
-H "Authorization: Bearer "
```
As an Authorization Server, the MCP Server must also be able to issue `access_token`s for clients in a secure fashion.
Before issuing a token, the Server will verify the Client's credentials, and, in some cases, the identity of the user trying to access the Server.
The Authorization Server will decide the characteristics of the token: its expiry, scope, intended audiences, etc.
Using Spring Security and Spring Authorization Server, we can easily add both capabilities to an existing Spring MCP Server.

## Adding OAuth2 to your Spring MCP server
In this example, we will add OAuth 2 support to a sample MCP Server - the ["Weather" MCP tool](https://github.com/spring-projects/spring-ai-examples/tree/main/model-context-protocol/weather/starter-webmvc-server) from our Spring AI examples repository.
We will not explore the client-side of the interaction, only ensure our server can issue tokens and validate them.
First, we import the required Boot starters in `pom.xml`:
```xml theme={null}
org.springframework.boot
spring-boot-starter-oauth2-resource-server
org.springframework.boot
spring-boot-starter-oauth2-authorization-server
```
Then, we configure an OAuth2 Client in our `application.properties`, so that we can request access tokens:
```properties theme={null}
spring.security.oauth2.authorizationserver.client.oidc-client.registration.client-id=mcp-client
spring.security.oauth2.authorizationserver.client.oidc-client.registration.client-secret={noop}secret
spring.security.oauth2.authorizationserver.client.oidc-client.registration.client-authentication-methods=client_secret_basic
spring.security.oauth2.authorizationserver.client.oidc-client.registration.authorization-grant-types=client_credentials
```
This is the simplest possible client.
We can interact with the authorization server directly by making POST requests, no browser needed, and use the hard-coded credentials `mcp-client` / `secret`.
The last step is to enable the authorization server and resource server features.
We do so by creating a configuration class for our security features, for example `SecurityConfiguration`, in which we expose a `SecurityFilterChain` bean:
```java theme={null}
import static org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer.authorizationServer;
@Configuration
@EnableWebSecurity
class SecurityConfiguration {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
.with(authorizationServer(), Customizer.withDefaults())
.oauth2ResourceServer(resource -> resource.jwt(Customizer.withDefaults()))
.csrf(CsrfConfigurer::disable)
.cors(Customizer.withDefaults())
.build();
}
}
```
This filter chain will do a number of things:
* Ensure every request is authenticated. With this, our MCP Server will only allow requests with an `access_token`.
* Enable both Spring Authorization Server and Spring Resource Server.
* Turn off CSRF (Cross-Site Request Forgery). An MCP server is not designed for browser-based interactions, and does not require CSRF.
* Turn on CORS (Cross-Origin Resource Sharing) support, so we can demo the server with the MCP inspector.
With this, our application is secured, and will only accept requests that have an access token.
Otherwise, requests will be rejected, with an `HTTP 401 Unauthorized` error. For example:
```shell theme={null}
curl http://localhost:8080/sse --fail-with-body
#
# Response:
#
# curl: (22) The requested URL returned error: 401
```
To use our MCP server, we need to obtain an access token first.
We use the `client_credentials` OAuth2 grant, which is used in "machine to machine" or "service account" scenarios:
```shell theme={null}
curl -XPOST http://localhost:8080/oauth2/token --data grant_type=client_credentials --user mcp-client:secret
#
# Response:
#
# {"access_token":"","token_type":"Bearer","expires_in":299}%
```
Copy the value of the `access_token`. It starts with letters "ey".
We can now use this access token to make requests, and they should succeed.
For example using `curl`, your can replace `YOUR_ACCESS_TOKEN` by the value you copied above:
```shell theme={null}
curl http://localhost:8080/sse -H"Authorization: Bearer YOUR_ACCESS_TOKEN"
#
# Response:
#
# id:918d5ebe-9ae5-4b04-aae8-c1ff8cdbb6e0
# event:endpoint
# data:/mcp/message?sessionId=918d5ebe-9ae5-4b04-aae8-c1ff8cdbb6e0
```
It is also possible to use the access token directly in the [MCP inspector](https://modelcontextprotocol.io/docs/tools/inspector), since version `0.6.0`.
Simply spin up the inspector, and paste the access token in the "Authentication > Bearer" field on the left-hand menu.
Then click Connect: you should be able to make MCP calls.

If you would like to run this yourself, you can check out [the sample code](https://github.com/spring-projects/spring-ai-examples/tree/main/model-context-protocol/weather/starter-webmvc-oauth2-server) in the `spring-ai-examples` repository.
## What's next?
In this example, we have implemented foundational OAuth2 capabilities in the MCP Server.
The obvious next step is to update MCP Client and allow it to authenticate with the Server, and use the "authorization code" OAuth2 grant.
With this flow, users can log in with their own credentials, and obtain user-bound tokens, allowing for more fine-grained permissions, for example with Roles-Based Access Control (RBAC).
We will also explore using an external OAuth2 Authorization Server for issuing tokens, and only implementing the Resource Server capabilities in our MCP Servers.
# Securing MCP Servers with Spring AI
Source: https://springaicommunity.mintlify.app/blog/mcp/mcp-server-security
[Model Context Protocol](https://modelcontextprotocol.io/), or MCP for short, has taken over the AI world.
[Model Context Protocol](https://modelcontextprotocol.io/), or MCP for short, has taken over the AI world.
If you've been following our blog, you've probably read the introduction to the
topic, [Connect Your AI to Everything: Spring AI's MCP Boot Starters](https://spring.io/blog/2025/09/16/spring-ai-mcp-intro-blog).
The security aspects of MCP have been evolving fast, and the latest version of the spec is getting more and more support
from the ecosystem.
To meet the needs of Spring users, we have incubated a dedicated project on
Github: [spring-ai-community/mcp-security](https://github.com/spring-ai-community/mcp-security/).
This week, we pushed our first releases, and you can now add them to your Spring AI 1.1.x-based applications.
In this post, we'll explore:
* [Securing MCP Servers with OAuth2](#securing-mcp-servers-with-oauth-2)
* [Building an MCP-compatible Spring Authorization Server](#mcp-compatible-spring-authorization-server)
* [Securing MCP Servers with API Keys](#beyond-oauth-2-api-keys) instead of OAuth2
## Securing MCP Servers with OAuth 2
According to the [Authorization section](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization)
of the MCP specification, MCP Servers exposed over HTTP must be secured with OAuth 2 access tokens.
Any call to an MCP Server must have a header `Authorization: Bearer `, where the access token is obtained
from an authorization server (think: Okta, Github, ...) on behalf of the user.
The MCP Server must also explicitly advertise the authorization servers it trusts, so MCP clients can discover them
dynamically, register themselves with the auth servers, and obtain tokens.
We'll discuss authorization servers later on, but for now we'll assume you have an auth server configured and running at
``, and we'll hook our MCP Server to it.
If you need to setup an authorization server, see [next section](#mcp-compatible-spring-authorization-server).
First, add the required dependencies to your project:
*Maven:*
```xml theme={null}
org.springframework.ai
spring-ai-starter-mcp-server-webmvc
org.springaicommunity
mcp-server-security
0.0.3
org.springframework.boot
spring-boot-starter-oauth2-resource-server
```
*Gradle:*
```groovy theme={null}
implementation("org.springframework.ai:spring-ai-starter-mcp-server-webmvc")
implementation("org.springaicommunity:mcp-server-security:0.0.3")
implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server")
```
Ensure that MCP server is enabled in your `application.properties`, and inject your authorization server URL:
```properties theme={null}
spring.ai.mcp.server.name=my-cool-mcp-server
# Supported protocols: STREAMABLE, STATELESS
spring.ai.mcp.server.protocol=STREAMABLE
# Choose any property name you'd like
# You MAY use the usual Spring well-known "spring.security.oauth2.resourceserver.jwt.issuer-uri".
authorization.server.url=
```
We will add a simple MCP tool that greets the user, based on an input language ("english", "french", ...) and on
the user's name.
```java theme={null}
@Service
public class MyToolsService {
@McpTool(name = "greeter", description = "A tool that greets you, in the selected language")
public String greet(
@ToolParam(description = "The language for the greeting (example: english, french, ...)") String language
) {
if (!StringUtils.hasText(language)) {
language = "";
}
var authentication = SecurityContextHolder.getContext().getAuthentication();
var name = authentication.getName();
return switch (language.toLowerCase()) {
case "english" -> "Hello, %s!".formatted(name);
case "french" -> "Salut %s!".formatted(name);
default -> ("I don't understand language \"%s\". " +
"So I'm just going to say Hello %s!").formatted(language, name);
};
}
}
```
In this example, the tool will look up the name of the user from the `SecurityContext`, and create a personalised
greeting.
The name of the user will be the `sub` claim from the JWT access token used to authenticate the request:
And, last but not least, we add a configuration class for security, for example `McpServerSecurityConfiguration`:
```java theme={null}
@Configuration
@EnableWebSecurity
class McpServerSecurityConfiguration {
@Value("${authorization.server.url}")
private String authServerUrl;
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
// Enforce authentication with token on EVERY request
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
// Configure OAuth2 on the MCP server
.with(
McpServerOAuth2Configurer.mcpServerOAuth2(),
(mcpAuthorization) -> {
// REQUIRED: the authserver's issuer URI
mcpAuthorization.authorizationServer(this.authServerUrl);
// OPTIONAL: enforce the `aud` claim in the JWT token.
mcpAuthorization.validateAudienceClaim(true);
}
)
.build();
}
}
```
Run the application with `./mvnw spring-boot:run` or `./gradlew bootRun`. It should start on port 8080.
If you try to access the MCP server at `http://localhost:8080/mcp`, you will get an `WWW-authenticate` indicating the
OAuth2 resource metadata URL:
```shell theme={null}
curl -XPOST -w '%{http_code}\n%header{www-authenticate}' http://localhost:8080/mcp
#
# Will print out:
#
# 401
# Bearer resource_metadata=http://localhost:8080/.well-known/oauth-protected-resource/mcp
```
The metadata URL itself will indicate to potential clients where the authorization server is located:
```shell theme={null}
curl http://localhost:8080/.well-known/oauth-protected-resource/mcp
#
# Will print out:
#
# {
# "resource": "http://localhost:8080/mcp",
# "authorization_servers": [
# ""
# ],
# "resource_name": "Spring MCP Resource Server",
# "bearer_methods_supported": [
# "header"
# ]
# }
```
This is not very useful to a human, but it helps other programs find the authentication entry points for your MCP
Server.
Every AI-based app has their own unique way of adding an MCP server, but a good tool to debug your server is the
[MCP inspector](https://modelcontextprotocol.io/docs/tools/inspector). You can run it easily with:
```shell theme={null}
npx @modelcontextprotocol/inspector@0.16.7
```
In the UI, you must set the URL of your server, and then click "Open Auth Settings":

In the auth settings, select the "Quick OAuth Flow".

This will redirect you to the authorization server.
Once you log in, you will be redirected back to the MCP inspector, which will display a success message and the first
few characters of an access token. From there, you should be able to connect and ultimately call our "greeter" tool:

In the screenshot above, in order, you can do:
1. Select the `Tools` tab
2. Click `List tools`
3. Select the `greeter` tool
4. Fill the arguments and call the tool
And with this, you have your very first spec-compliant, OAuth2-secured MCP Server.
There are variants around this implementation, for example a use-case where everything on the MCP server is publicly
accessible (e.g. "list tools"), except calling the tools themselves.
It is not spec compliant but matches certain specific needs.
You can learn
more [in the dedicated section](https://github.com/spring-ai-community/mcp-security/?tab=readme-ov-file#special-case-only-secure-tool-calls-with-oauth2)
in the mcp-security docs.
Of course, for users to log in, you must connect your MCP Server to an authorization server, that complies with the
required specifications for MCP, such as dynamic client registration.
While there are many SaaS options available, you can also write you own
with [Spring Authorization Server](https://docs.spring.io/spring-authorization-server/).
## MCP-compatible Spring Authorization Server
To create an MCP-compatible authorization server with Spring, create a new Spring project, with Spring Authorization
Server, and add the MCP-specific:
*Maven*
```xml theme={null}
org.springaicommunity
mcp-authorization-server
0.0.3
```
*Gradle*
```groovy theme={null}
implementation("org.springaicommunity:mcp-authorization-server:0.0.3")
```
You can configure the Authorization Server in the usual way (
see [reference documentation](https://docs.spring.io/spring-security/reference/7.0/servlet/oauth2/authorization-server/getting-started.html#oauth2AuthorizationServer-developing-your-first-application)).
Here is an example `application.yml` for registering a default client and a default user:
```yaml theme={null}
spring:
application:
name: sample-authorization-server
security:
oauth2:
authorizationserver:
client:
default-client:
token:
access-token-time-to-live: 1h
registration:
client-id: "default-client-id"
client-secret: "{noop}default-client-secret"
client-authentication-methods:
- "client_secret_basic"
- "none"
authorization-grant-types:
- "authorization_code"
- "client_credentials"
redirect-uris:
- "http://127.0.0.1:8080/authorize/oauth2/code/authserver"
- "http://localhost:8080/authorize/oauth2/code/authserver"
# mcp-inspector
- "http://localhost:6274/oauth/callback"
user:
# A single user, named "user"
name: user
password: password
server:
port: 9000
servlet:
session:
cookie:
# Override the default cookie name (JSESSIONID).
# This allows running multiple Spring apps on localhost, and they'll each have their own cookie.
# Otherwise, since the cookies do not take the port into account, they are confused.
name: MCP_AUTHORIZATION_SERVER_SESSIONID
```
This is only an example, and you'll likely want to write your own configuration.
With this configuration, there will be a single user registered (username: `user`, password: `password`).
There will also be a single OAuth2 Client (`default-client-id` / `default-client-secret`).
You can then activate all the authorization server capabilities with the usual Spring Security API,
the security filter chain:
```java theme={null}
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
// all requests must be authenticated
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
// enable authorization server customizations
.with(McpAuthorizationServerConfigurer.mcpAuthorizationServer(), withDefaults())
// enable form-based login, for user "user"/"password"
.formLogin(withDefaults())
.build();
}
```
With this, your Spring Authorization Server will
support [OAuth 2 Dynamic Client Registration](https://www.rfc-editor.org/rfc/rfc7591.html) as well as [Resource
Indicators for OAuth 2](https://www.rfc-editor.org/rfc/rfc8707.html).
Connecting your MCP Server to this authorization server is compatible with the majority of AI tools,
such as Claude Desktop, Cursor, or the MCP inspector.
## Beyond OAuth 2: API keys
While the MCP specification mandates using OAuth2 for security, many environments do not have the infrastructure to
support this use-case.
To be usable in environments lacking OAuth 2, many clients, including the MCP inspector itself, allow you to pass custom
headers when making requests.
This opens the door to alternative authentication flows, including API key-based security.
The MCP Security project supports API keys, which we'll showcase below.
First, add the dependencies to your project:
```xml theme={null}
org.springframework.ai
spring-ai-starter-mcp-server-webmvc
org.springaicommunity
mcp-server-security
0.0.3
org.springframework.boot
spring-boot-starter-security
```
*Gradle:*
```groovy theme={null}
implementation("org.springframework.ai:spring-ai-starter-mcp-server-webmvc")
implementation("org.springaicommunity:mcp-server-security:0.0.3")
implementation("org.springframework.boot:spring-boot-starter-security")
```
Ensure that MCP server is enabled in your `application.properties`:
```properties theme={null}
spring.ai.mcp.server.name=my-cool-mcp-server
# Supported protocols: STREAMABLE, STATELESS
spring.ai.mcp.server.protocol=STREAMABLE
```
"Entites" authenticated by an API key, such as users or service accounts, are represented by `ApiKeyEntity`. The MCP
server checks a specific header for an API key, loads the entity, and validates the secret.
You can bring your own entity implementation, and your own entity repository, for specific security validations.
With that, you can configure the security for your project in the usual Spring-Security way:
```java theme={null}
@Configuration
@EnableWebSecurity
class McpServerConfiguration {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http.authorizeHttpRequests(authz -> authz.anyRequest().authenticated())
.with(
McpApiKeyConfigurer.mcpServerApiKey(),
(apiKey) -> apiKey.apiKeyRepository(apiKeyRepository())
)
.build();
}
private ApiKeyEntityRepository apiKeyRepository() {
var apiKey = ApiKeyEntityImpl.builder()
.name("test api key")
.id("api01")
.secret("mycustomapikey")
.build();
return new InMemoryApiKeyEntityRepository<>(List.of(apiKey));
}
}
```
Here we use an API Key repository that stores simple keys.
Then you should be able to call your MCP server with a header `X-API-key: api01.mycustomapikey`. `X-API-key` is the
default header name for passing API keys, followed by the header value `{id}.{secret}`.
The secret is stored in bcrypt-hashed form on the server side.
The `mcpServerApiKey()` configurer offers options for changing the header name, and even dedicated APIs to extract the
API key from incoming HTTP requests.
## Improving MCP security
If you would like to learn more, head over to
the [spring-ai-community/mcp-security](https://github.com/spring-ai-community/mcp-security/) project, for documentation
and samples.
You will also find support for client-side MCP security with Spring AI and Spring Security.
Try it out with your own projects and applications, test it with the rest of the ecosystem, and help us improve it!
We are open to contributions, including feedback and issues.
In another blog post, we'll cover how to implement OAuth 2 on the client side, with the
`org.springaiframework:mcp-client-security` module.
# Prompt Caching Support in Spring AI with Anthropic Claude
Source: https://springaicommunity.mintlify.app/blog/model-providers/anthropic-prompt-caching
Large language model API costs can accumulate quickly when applications repeatedly send the same prompt content. A typical scenario: you're building a document...
Large language model API costs can accumulate quickly when applications repeatedly send the same prompt content. A typical scenario: you're building a document analyzer that includes a 3,000-token document in every request. Five questions about that document means processing 15,000 tokens of identical content at full price.
Anthropic's prompt caching addresses this by allowing you to reuse previously processed prompt segments. Spring AI provides comprehensive support through strategic caching patterns that handle cache breakpoint placement and management automatically.
In this blog post, we explain how prompt caching works, when to use it, and how Spring AI simplifies its implementation for you.
## Understanding Prompt Caching
Prompt caching allows you to mark portions of your prompt for reuse across multiple API requests. When you enable it, Anthropic caches the specified content and charges reduced rates for cached segments in subsequent requests.
### How It Works
The cache operates on exact prefix matching. Consider this sequence:
```
Request 1: [System Prompt] + [User: "Question 1"]
└─ Cached ──┘
Request 2: [System Prompt] + [User: "Question 2"]
└─ Cache Hit ─┘ (Only this part incurs full cost)
```
The system generates cache keys using cryptographic hashes of prompt content up to designated cache control points. Only requests with identical content achieve cache hits—even a single character change creates a new cache entry.
```
Request 1: "You are a helpful assistant." → Cache created
Request 2: "You are a helpful assistant." → Cache hit
Request 3: "You are a helpful assistant " → Cache miss (extra space)
Request 4: "You are a Helpful assistant." → Cache miss (capitalization)
```
On a cache miss, the system processes content at standard rates and creates a new cache entry. This is why maintaining consistent prompt templates becomes important for effective caching.
**Performance Impact:** Beyond cost savings, Anthropic reports latency reductions of up to 85% for long prompts. In their [announcement](https://www.anthropic.com/news/prompt-caching), a 100K-token book example showed response time dropping from 11.5s to 2.4s with caching enabled. Note that actual latency improvements depend on how much content you're caching and your cache hit rate.
**Cache Lifecycle:** Entries refresh on each use within the TTL window (5 minutes default, 1 hour optional). After TTL expiration, the next request creates a new cache entry.
### Cost Structure
Pricing varies significantly by model tier:
| Model | Base Input | Cache Write | Cache Read | Savings |
| --------------------- | ----------- | ------------------- | ----------- | ------- |
| **Claude Sonnet 4.5** | \$3/MTok | \$3.75/MTok (+25%) | \$0.30/MTok | **90%** |
| **Claude Sonnet 4** | \$3/MTok | \$3.75/MTok (+25%) | \$0.30/MTok | **90%** |
| **Claude Opus 4.1** | \$15/MTok | \$18.75/MTok (+25%) | \$1.50/MTok | **90%** |
| **Claude Opus 4** | \$15/MTok | \$18.75/MTok (+25%) | \$1.50/MTok | **90%** |
| **Claude Haiku 4.5** | \$1/MTok | \$1.25/MTok (+25%) | \$0.10/MTok | **90%** |
| **Claude Haiku 3.5** | \$0.80/MTok | \$1/MTok (+25%) | \$0.08/MTok | **90%** |
| **Claude Haiku 3** | \$0.25/MTok | \$0.30/MTok (+25%) | \$0.03/MTok | **90%** |
*All code examples in this blog use Claude Sonnet 4.5 pricing unless otherwise specified.*
For more on pricing, see - [Anthropic pricing on prompt caching](https://docs.claude.com/en/docs/build-with-claude/prompt-caching#pricing)
**Example calculation (Claude 3.5 Sonnet, 5,000 tokens):**
* First request: 5,000 tokens × $3.75/M = **$0.01875\*\* (cache write)
* Subsequent requests: 5,000 tokens × $0.30/M = **$0.00150\*\* (cache read)
* **Savings:** 90% reduction on cached content
* **Breakeven:** 2nd request (the 25% write premium is recovered immediately)
### Requirements and Limitations
**Minimum token thresholds vary by model:**
| Model | Minimum Cacheable Tokens |
| -------------------------------------------------------------------- | ------------------------ |
| Claude Sonnet 4.5, , Claude Sonnet 4, Claude Opus 4.1, Claude Opus 4 | 1,024 |
| Claude Haiku 3.5, Claude Haiku 3 | 2,048 |
| Claude Haiku 4.5 | **4,096** |
Prompts below these thresholds cannot be cached, even when you mark them with cache control directives. Note that Claude Haiku 4.5 requires significantly more tokens (4,096) compared to other models.
For more on this, see [Anthropic's cacheable prompt length limitations](https://docs.claude.com/en/docs/build-with-claude/prompt-caching#cache-limitations).
**Additional constraints:**
* Maximum 4 cache breakpoints per request
* Cache TTL: 5 minutes default, 1 hour optional (at higher write cost)
* Cache refreshes on each use within TTL window
* Cache entries become available after the first response begins (not available for concurrent parallel requests)
## Cache Hierarchy and Cascade Invalidation
Anthropic processes request components in a specific order, and this order determines how cache invalidation works.
```
┌─────────────────────────────────────────┐
│ Request Processing Order: │
│ │
│ 1. Tools │
│ ↓ │
│ 2. System Message │
│ ↓ │
│ 3. Message History │
└─────────────────────────────────────────┘
```
Changes cascade downward through this hierarchy:
```
┌──────────────────────────────────────────────────────┐
│ Cascade Invalidation: │
│ │
│ Change Tools → Invalidates: Tools, System, Msgs │
│ Change System → Invalidates: System, Msgs │
│ Change Messages → Invalidates: Msgs only │
└──────────────────────────────────────────────────────┘
```
Understanding this behavior is essential when choosing your caching strategy. Changes to components higher in the hierarchy invalidate all downstream caches, which directly impacts your cache hit rates.
## Spring AI Cache Strategies
Rather than requiring you to manually place cache breakpoints (which can be error-prone and tedious), Spring AI provides five strategic patterns through the `AnthropicCacheStrategy` enum. Each strategy handles cache control directive placement automatically while respecting Anthropic's 4-breakpoint limit.
### Strategy Overview
| Strategy | Breakpoints | Cached Content | Typical Use Case |
| ---------------------- | ----------- | ----------------- | ----------------------------------- |
| `NONE` | 0 | Nothing | One-off requests, testing |
| `SYSTEM_ONLY` | 1 | System message | Stable system prompts, \<20 tools |
| `TOOLS_ONLY` | 1 | Tool definitions | Large tools, dynamic system prompts |
| `SYSTEM_AND_TOOLS` | 2 | Tools + System | 20+ tools, both stable |
| `CONVERSATION_HISTORY` | 1-4 | Full conversation | Multi-turn conversations |
Let's take a look at each strategy with practical examples.
## SYSTEM\_ONLY Strategy
This strategy caches the system message content. Since tools appear before the system message in Anthropic's request hierarchy (Tools → System → Messages), they automatically become part of the cache prefix when you place a cache breakpoint on the system message.
**Important:** Changing any tool definition will invalidate the system cache due to the cache hierarchy.
```java theme={null}
String systemPrompt = """
You are an expert software architect specializing in distributed systems.
You have deep knowledge of microservices, event-driven architecture, and cloud-native patterns.
When analyzing systems, consider scalability, resilience, and maintainability.
[... additional context ...]
""";
ChatResponse response = chatModel.call(
new Prompt(
List.of(
new SystemMessage(systemPrompt),
new UserMessage("What is microservices architecture?")
),
AnthropicChatOptions.builder()
.model(AnthropicApi.ChatModel.CLAUDE_3_5_SONNET)
.cacheOptions(AnthropicCacheOptions.builder()
.strategy(AnthropicCacheStrategy.SYSTEM_ONLY)
.build())
.maxTokens(500)
.build()
)
);
// Access cache metrics
AnthropicApi.Usage usage = (AnthropicApi.Usage) response.getMetadata()
.getUsage().getNativeUsage();
if (usage != null) {
System.out.println("Cache creation: " + usage.cacheCreationInputTokens());
System.out.println("Cache read: " + usage.cacheReadInputTokens());
}
```
On the first request, `cacheCreationInputTokens` will be greater than zero and `cacheReadInputTokens` will be zero. Subsequent requests with the same system prompt will show zero for `cacheCreationInputTokens` and a positive value for `cacheReadInputTokens`. This is how you can verify that caching is working as expected in your application.
Use this strategy when your system prompt is large (meeting the minimum token threshold) and stable, but user questions vary.
## TOOLS\_ONLY Strategy
This strategy caches tool definitions while processing the system message fresh on each request. The use case becomes clear in multi-tenant scenarios where tools are shared but system prompts need customization.
Consider a SaaS application serving multiple organizations:
```java theme={null}
@Service
public class MultiTenantAIService {
private final List sharedTools = List.of(
weatherTool, // 500 tokens
calendarTool, // 800 tokens
emailTool, // 700 tokens
analyticsTool, // 600 tokens
reportingTool, // 900 tokens
// ... 15 more tools totaling 5,000+ tokens
);
public String handleRequest(String tenantId, String userQuery) {
TenantConfig config = tenantRepository.findById(tenantId);
// Each tenant requires a unique system prompt
String systemPrompt = """
You are %s's AI assistant.
Company values: [custom data]
Brand voice: [custom data]
Compliance requirements: [custom data]
""";
ChatResponse response = chatModel.call(
new Prompt(
List.of(
new SystemMessage(systemPrompt),
new UserMessage(userQuery)
),
AnthropicChatOptions.builder()
.model(AnthropicApi.ChatModel.CLAUDE_3_5_SONNET)
.cacheOptions(AnthropicCacheOptions.builder()
.strategy(AnthropicCacheStrategy.TOOLS_ONLY)
.build())
.toolCallbacks(sharedTools)
.maxTokens(800)
.build()
)
);
return response.getResult().getOutput().getText();
}
}
```
Here's what happens:
* **First request (any tenant)**: Tools cached at 1.25x cost
* **All subsequent requests (all tenants)**: Tools read from cache at 0.1x cost
* **Each tenant's system prompt**: Processed fresh at 1.0x cost (by design)
All tenants share the same cached tool definitions, while each receives their customized system prompt. For the 5,000-token tool set, this means paying `$0.01875` once to create the cache, then `$0.0015` per request for cache reads—regardless of which tenant is making the request.
## SYSTEM\_AND\_TOOLS Strategy
This strategy creates two independent cache breakpoints: one for tools (breakpoint 1) and one for the system message (breakpoint 2). This separation matters when you have more than 20 tools or when you need deterministic caching of both components.
The key advantage: changing the system message does not invalidate the tool cache.
```java theme={null}
ChatResponse response = chatModel.call(
new Prompt(
List.of(
new SystemMessage(systemPrompt),
new UserMessage(userQuery)
),
AnthropicChatOptions.builder()
.model(AnthropicApi.ChatModel.CLAUDE_3_5_SONNET)
.cacheOptions(AnthropicCacheOptions.builder()
.strategy(AnthropicCacheStrategy.SYSTEM_AND_TOOLS)
.build())
.toolCallbacks(toolCallbacks)
.maxTokens(500)
.build()
)
);
```
The cache keys work as follows:
* **Breakpoint 1 (tools)**: `hash(tools)`
* **Breakpoint 2 (system)**: `hash(tools + system)`
**Cascade Behavior:**
* **System changes only:** Tool cache (breakpoint 1) remains valid, system cache (breakpoint 2) invalidated
* **Tool changes:** Both caches invalidated
This makes SYSTEM\_AND\_TOOLS ideal when your system prompt changes more frequently than your tools, allowing efficient reuse of the tool cache.
## CONVERSATION\_HISTORY Strategy
For multi-turn conversations, this strategy caches the entire conversation history incrementally. Spring AI places a cache breakpoint on the last user message in the conversation history. This is particularly useful when building conversational AI applications (such as chatbots, virtual assistants, and customer support systems).
```java theme={null}
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultSystem("You are a personalized career counselor with 20 years of experience...")
.defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory)
.conversationId(conversationId)
.build())
.build();
String response = chatClient.prompt()
.user("What career advice would you give me based on our conversation?")
.options(AnthropicChatOptions.builder()
.model(AnthropicApi.ChatModel.CLAUDE_3_5_SONNET)
.cacheOptions(AnthropicCacheOptions.builder()
.strategy(AnthropicCacheStrategy.CONVERSATION_HISTORY)
.build())
.maxTokens(500)
.build())
.call()
.content();
```
The cache grows incrementally as the conversation progresses:
```
Turn 1: Cache [U1]
Turn 2: Reuse [U1], cache [U1 + A1 + U2]
Turn 3: Reuse [U1 + A1 + U2], cache [U1 + A1 + U2 + A2 + U3]
Turn 4: Reuse [U1 + A1 + U2 + A2 + U3], cache [full history + U4]
```
By turn 10 in a conversation, you might have 20,000 tokens of history cached, paying just 10% of the normal cost for that context on each subsequent turn.
**Critical Requirement: Tool and System Stability**
When using `CONVERSATION_HISTORY`, both tools and system prompts must remain stable throughout the conversation. Changes to either invalidate the entire conversation cache.
## Example: Partnership Agreement Analysis
Here's a complete example showing cache efficiency for multi-question document analysis:
```java theme={null}
@Service
public class ContractAnalyzer {
private final AnthropicChatModel chatModel;
private final PdfExtractor pdfExtractor;
public AnalysisReport analyzeAgreement(String agreementPdf) {
// Extract text from PDF (typically 2,000-5,000 tokens)
String agreementText = pdfExtractor.extract(agreementPdf);
String systemPrompt = """
You are an expert business analyst specializing in partnership agreements.
Analyze the following partnership agreement and provide insights about
collaboration terms, value propositions, and strategic opportunities.
AGREEMENT:
%s
""".formatted(agreementText);
String[] questions = {
"What are the key collaboration opportunities outlined in this agreement?",
"Summarize the revenue sharing and financial arrangements.",
"What intellectual property rights and licensing terms are defined?",
"Identify the strategic value propositions for both parties.",
"What are the performance milestones and success metrics?"
};
AnalysisReport report = new AnalysisReport();
AnthropicChatOptions options = AnthropicChatOptions.builder()
.model(AnthropicApi.ChatModel.CLAUDE_3_5_SONNET)
.cacheOptions(AnthropicCacheOptions.builder()
.strategy(AnthropicCacheStrategy.SYSTEM_ONLY)
.messageTypeTtl(MessageType.SYSTEM, AnthropicCacheTtl.ONE_HOUR)
.build())
.maxTokens(1000)
.build();
for (int i = 0; i < questions.length; i++) {
ChatResponse response = chatModel.call(
new Prompt(
List.of(
new SystemMessage(systemPrompt),
new UserMessage(questions[i])
),
options
)
);
String answer = response.getResult().getOutput().getText();
report.addSection(questions[i], answer);
// Log cache performance
AnthropicApi.Usage usage = (AnthropicApi.Usage)
response.getMetadata().getUsage().getNativeUsage();
if (usage != null) {
if (i == 0) {
logger.info("First question - Cache created: {} tokens",
usage.cacheCreationInputTokens());
} else {
logger.info("Question {} - Cache read: {} tokens",
i + 1, usage.cacheReadInputTokens());
}
}
}
return report;
}
}
```
With a 3,500-token system prompt (agreement + instructions) using Claude 3.5 Sonnet:
* **First question**: 3,500 tokens × $3.75/M = $0.013 (cache write)
* **Questions 2-5**: 3,500 tokens × $0.30/M = $0.001 (cache read) each
* **Total cached content cost**: $0.013 + (4 × $0.001) = \$0.017
* **Without caching**: 5 × (3,500 tokens × $3.00/M) = $0.053
This represents a 68% cost reduction for the cached system prompt portion. The actual total savings will be lower when you factor in the user question tokens and output tokens (which are not cached), but the reduction becomes more significant with more questions or larger documents.
## Getting Started
**Note:** Prompt caching support is available in Spring AI `1.1.0` and later. Try it with the latest `1.1.0-SNAPSHOT` version.
Add the Spring AI Anthropic starter to your project:
```xml theme={null}
org.springframework.ai
spring-ai-anthropic-spring-boot-starter
```
Configure your API key in your application properties:
```properties theme={null}
spring.ai.anthropic.api-key=${ANTHROPIC_API_KEY}
```
Inject and use in your application:
```java theme={null}
@Autowired
private AnthropicChatModel chatModel;
// Start using caching strategies immediately
```
## Advanced Configuration Options
### Extended Cache TTL
The default cache TTL is 5 minutes. For scenarios where requests arrive less frequently, you can configure a 1-hour cache:
```java theme={null}
AnthropicChatOptions options = AnthropicChatOptions.builder()
.model(AnthropicApi.ChatModel.CLAUDE_3_5_SONNET)
.cacheOptions(AnthropicCacheOptions.builder()
.strategy(AnthropicCacheStrategy.SYSTEM_ONLY)
.messageTypeTtl(MessageType.SYSTEM, AnthropicCacheTtl.ONE_HOUR)
.build())
.maxTokens(500)
.build();
```
Spring AI automatically adds the required beta header (`anthropic-beta: extended-cache-ttl-2025-04-11`) when you configure 1-hour TTL.
**When to use each TTL:**
* **5 minutes (default):** Real-time conversations, frequently updated content
* **1 hour:** Infrequent requests (>5 min apart), stable reference materials, lower traffic
**Trade-off:** 1-hour cache writes cost **2x** vs 5-minute writes ($6/M vs $3.75/M for Claude 3.5 Sonnet). Evaluate your application's request patterns and stability requirements to determine which TTL makes sense.
### Content Length Filtering
You can set minimum content lengths per message type to optimize breakpoint usage:
```java theme={null}
AnthropicCacheOptions cache = AnthropicCacheOptions.builder()
.strategy(AnthropicCacheStrategy.CONVERSATION_HISTORY)
.messageTypeMinContentLength(MessageType.SYSTEM, 1024)
.messageTypeMinContentLength(MessageType.USER, 512)
.messageTypeMinContentLength(MessageType.ASSISTANT, 512)
.build();
```
For precise token counting, you can provide a custom length function:
```java theme={null}
AnthropicCacheOptions cache = AnthropicCacheOptions.builder()
.strategy(AnthropicCacheStrategy.CONVERSATION_HISTORY)
.contentLengthFunction(text -> customTokenCounter.count(text))
.build();
```
By default, Spring AI uses string length as a proxy for token count, but for production scenarios, you might want to consider injecting a proper token counter.
## Implementation Details
For those interested in internals, here's how Spring AI handles cache management:
```
┌─────────────────────────────────────────────────────────┐
│ Request Flow: │
│ │
│ Application │
│ ↓ │
│ AnthropicChatModel │
│ ↓ │
│ CacheEligibilityResolver │
│ (created from strategy) │
│ ↓ │
│ For each component: │
│ - Check strategy eligibility │
│ - Verify minimum content length │
│ - Confirm breakpoint availability (<4) │
│ - Add cache_control if eligible │
│ ↓ │
│ Build request with cache markers │
│ ↓ │
│ Add beta headers if needed (1h TTL) │
│ ↓ │
│ Anthropic API │
│ ↓ │
│ Response with cache metrics │
└─────────────────────────────────────────────────────────┘
```
The `CacheEligibilityResolver` determines whether each message or tool qualifies for caching based on the chosen strategy, message type eligibility, content length requirements, and available breakpoints. The `CacheBreakpointTracker` enforces Anthropic's 4-breakpoint limit with thread-safe tracking per request.
For `CONVERSATION_HISTORY`, Spring AI uses aggregate eligibility checking—it considers the combined content of all message types (user, assistant, tool) within the last \~20 content blocks when determining cache eligibility. This prevents short user questions (such as "Tell me more") from blocking cache creation when there are substantial assistant responses in the conversation history.
## Practical Considerations
### When Caching Doesn't Help
Avoid caching when:
* Content changes frequently (cache miss rate >50%)
* Prompts are below minimum token thresholds for your model
* Making one-off requests with no reuse patterns
* Tools or system prompts change more often than content is reused
### Strategy Anti-Patterns
Avoid these common mistakes:
* **Don't use SYSTEM\_ONLY** if your system prompt changes frequently—you'll pay cache write costs without getting cache hits
* **Don't use TOOLS\_ONLY** if your tools change frequently—you'll pay cache write costs without getting cache hits. Note that SYSTEM\_ONLY won't help either when tools change frequently, since tool changes invalidate the system cache
* **Don't use CONVERSATION\_HISTORY** if you can't guarantee tool and system stability—changes invalidate the entire conversation cache
* **Don't use SYSTEM\_AND\_TOOLS** if you only have a few small tools (\<20)—SYSTEM\_ONLY's implicit caching is sufficient
### Streaming Support
Caching works seamlessly with both streaming and non-streaming responses. Cache metrics appear in the final response chunk when using streaming. There is no difference in cache behavior between the two modes.
## Conclusion
Prompt caching in Anthropic Claude provides significant cost and latency benefits for applications with reusable prompt content. Spring AI's strategic approach simplifies implementation by automatically handling cache breakpoint placement, breakpoint limits, and TTL configuration.
The five caching strategies cover common usage patterns, from simple system prompt caching to complex multi-turn conversations. For most applications, selecting the appropriate strategy based on content stability patterns is sufficient—Spring AI handles the implementation details.
For additional information, see the [Spring AI Anthropic documentation](https://docs.spring.io/spring-ai/reference/api/chat/anthropic-chat.html#_prompt_caching) and [Anthropic's prompt caching guide](https://docs.claude.com/en/docs/build-with-claude/prompt-caching).
# Introducing Spring AI Amazon Bedrock Nova Integration via Converse API
Source: https://springaicommunity.mintlify.app/blog/model-providers/bedrock-nova
The [Amazon Bedrock Nova](https://docs.aws.amazon.com/nova/latest/userguide/what-is-nova.html) models represent a new generation of foundation models supporting...
The [Amazon Bedrock Nova](https://docs.aws.amazon.com/nova/latest/userguide/what-is-nova.html) models represent a new generation of foundation models supporting a broad range of use cases, from text and image understanding to video-to-text analysis.
With the [Spring AI Bedrock Converse API](https://docs.spring.io/spring-ai/reference/api/chat/bedrock-converse.html) integration, developers can seamlessly connect to these advanced Nova models and build sophisticated conversational applications with minimal effort.
This blog post introduces the key features of Amazon Nova models, demonstrates their integration with Spring AI's Bedrock Converse API, and provides practical examples for text, image, video, document processing, and function calling.
## What are Amazon Nova Models?
Amazon Nova offers three tiers of models—Nova Pro, Nova Lite, and Nova Micro—to address different performance and cost requirements:
| Specification | Nova Pro | Nova Lite | Nova Micro |
| ------------- | -------------------------- | -------------------------- | ---------------------- |
| Modalities | Text, Image, Video-to-text | Text, Image, Video-to-text | Text |
| Model ID | amazon.nova-pro-v1:0 | amazon.nova-lite-v1:0 | amazon.nova-micro-v1:0 |
| Max tokens | 300K | 300K | 128K |
Nova Pro and Lite support multimodal capabilities, including text, image, and video inputs, while Nova Micro is optimized for text-only interactions at a lower cost.
## Setting Up the Integration
### Prerequisites
1. **AWS Configuration**: You need:
* AWS credentials with access to Amazon Bedrock
* Necessary permissions to use Nova models
* Models enabled in the [Amazon Bedrock console](https://us-east-1.console.aws.amazon.com/bedrock/home?region=us-east-1#/modelaccess)
2. **Spring AI Dependency**:
Add the Spring AI Bedrock Converse starter to your Spring Boot project:
**Maven**:
```xml theme={null}
org.springframework.ai
spring-ai-bedrock-converse-spring-boot-starter
```
**Gradle**:
```groovy theme={null}
dependencies {
implementation 'org.springframework.ai:spring-ai-bedrock-converse-spring-boot-starter'
}
```
3. **Application Configuration**:
Configure `application.properties` for Amazon Bedrock:
```properties theme={null}
spring.ai.bedrock.aws.region=us-east-1
spring.ai.bedrock.aws.access-key=${AWS_ACCESS_KEY_ID}
spring.ai.bedrock.aws.secret-key=${AWS_SECRET_ACCESS_KEY}
spring.ai.bedrock.aws.session-token=${AWS_SESSION_TOKEN}
spring.ai.bedrock.converse.chat.options.model=amazon.nova-pro-v1:0
spring.ai.bedrock.converse.chat.options.temperature=0.8
spring.ai.bedrock.converse.chat.options.max-tokens=1000
```
For more details, refer to the [Chat Properties](https://docs.spring.io/spring-ai/reference/api/chat/bedrock-converse.html#_chat_properties) documentation.
## Key Features of the Bedrock Nova Integration
### 1. Text Completion
Text-based chat completion is straightforward:
```java theme={null}
String response = ChatClient.create(chatModel)
.prompt("Tell me a joke about AI.")
.call()
.content();
```
### 2. Multimodal Input
Nova Pro and Lite support multimodal inputs, enabling text and visual data processing. Spring AI provides a portable [Multimodal API](https://docs.spring.io/spring-ai/reference/api/multimodality.html) that supports Bedrock Nova models.
#### Text + Image
Nova Pro and Lite support multiple [image modalities](https://docs.aws.amazon.com/nova/latest/userguide/modalities-image.html). These models can analyze images, answer questions about them, classify them, and generate summaries based on provided instructions. They support base64-encoded images in `image/jpeg`, `image/png`, `image/gif`, and `image/webp` formats.
Example combining user text with an image:
```java theme={null}
String response = ChatClient.create(chatModel)
.prompt()
.user(u -> u.text("Explain what do you see on this picture?")
.media(Media.Format.IMAGE_PNG, new ClassPathResource("/test.png")))
.call()
.content();
```
This code processes the `test.png` image:
with the text message `"Explain what do you see on this picture?"` and generates a response like:
> The image shows a close-up view of a wire fruit basket containing several pieces of fruit...
#### Text + Video
Amazon Nova Pro/Lite models support a single [video modality](https://docs.aws.amazon.com/nova/latest/userguide/modalities-video.html) in the payload, provided either in base64 format or through an Amazon S3 URI.
Supported video formats include `video/x-matros`, `video/quicktime`, `video/mp4`, `video/webm`, `video/x-flv`, `video/mpeg`, `video/x-ms-wmv`, and `image/3gpp`.
Example combining user text with a video:
```java theme={null}
String response = ChatClient.create(chatModel)
.prompt()
.user(u -> u.text("Explain what do you see in this video?")
.media(Media.Format.VIDEO_MP4, new ClassPathResource("/test.video.mp4")))
.call()
.content();
```
This code processes the `test.video.mp4` video
with the text message `"Explain what do you see in this video?"` and generates a response like:
> The video shows a group of baby chickens, also known as chicks, huddled together on a surface ...
#### Text + Document
Nova Pro/Lite supports [document modalities](https://docs.aws.amazon.com/nova/latest/userguide/modalities-document.html) in two variants:
* Text document types (txt, csv, html, md, etc.) for text understanding and answering questions based on textual elements
* Media document types (pdf, docx, xlsx) for vision-based understanding, such as analyzing charts and graphs
Example combining user text with a media document:
```java theme={null}
String response = ChatClient.create(chatModel)
.prompt()
.user(u -> u.text(
"You are a very professional document summarization specialist. Please summarize the given document.")
.media(Media.Format.DOC_PDF, new ClassPathResource("/spring-ai-reference-overview.pdf")))
.call()
.content();
```
This code processes the `spring-ai-reference-overview.pdf` document:
with the text message and generates a response like:
> **Introduction:**
>
> * Spring AI is designed to simplify the development of applications with artificial intelligence (AI) capabilities, aiming to avoid unnecessary complexity....
### 3. Function Calling
Nova models support [Tool/Function Calling](https://docs.spring.io/spring-ai/reference/api/functions.html) for integration with external tools.
#### Define a Function
```java theme={null}
@Bean
@Description("Get the weather in a location. Return temperature in Celsius or Fahrenheit.")
public Function weatherFunction() {
return new MockWeatherService();
}
```
#### Use the Function in a Chat Prompt
```java theme={null}
String response = ChatClient.create(this.chatModel)
.prompt("What's the weather like in Boston?")
.function("weatherFunction") // bean name
.inputType(WeatherRequest.class)
.call()
.content();
```
## Resources
#### Getting Started
* [Spring AI Documentation](https://docs.spring.io/spring-ai/reference/index.html) - Comprehensive guide to Spring AI
* [Spring AI Bedrock Converse API Guide](https://docs.spring.io/spring-ai/reference/api/chat/bedrock-converse.html) - Detailed API documentation
#### Amazon Bedrock Resources
* [Amazon Bedrock Nova Documentation](https://docs.aws.amazon.com/nova/latest/userguide/what-is-nova.html) - Official Nova models documentation
* [Amazon Bedrock Console](https://us-east-1.console.aws.amazon.com/bedrock/home) - Manage and monitor your Bedrock resources
* [Nova Model Capabilities](https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters.html) - Detailed information about Nova model parameters and capabilities
#### Code Examples
* [Spring AI Bedrock Nova Demo](https://github.com/tzolov/spring-ai-bedrock-nova-demo) - Complete example project showcasing integration features
* [BedrockNovaChatClientIT.java](https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-bedrock-converse/src/test/java/org/springframework/ai/bedrock/converse/client/BedrockNovaChatClientIT.java) - Integration test examples
* [Spring AI Samples Repository](https://github.com/spring-projects/spring-ai/tree/main/spring-ai-samples) - Additional code samples and use cases
## Tanzu AI Server
[VMware Tanzu Platform 10](https://blogs.vmware.com/tanzu/broadcom-announces-the-general-availability-of-vmware-tanzu-platform-10-making-it-easier-for-customers-to-build-and-launch-new-applications-in-the-private-cloud/) integrates Amazon Bedrock Nova models through the VMware Tanzu AI Server, powered by Spring AI.
This integration provides:
* **Enterprise-Grade AI Deployment**: Production-ready solution for deploying AI applications within your VMware Tanzu environment
* **Simplified Model Access**: Streamlined access to Amazon Bedrock Nova models through a unified interface
* **Security and Governance**: Enterprise-level security controls and governance features
* **Scalable Infrastructure**: Built on Spring AI, the integration supports for scalable deployment of AI applications while maintaining high performance
For more information about deploying AI applications with Tanzu AI Server, visit the [VMware Tanzu AI documentation](https://www.vmware.com/solutions/app-platform/ai).
## Conclusion
The integration of Spring AI with Amazon Bedrock Nova models via the Converse API enables powerful capabilities for building advanced conversational applications. Nova Pro and Lite provide comprehensive tools for developing multimodal experiences across text, images, videos, and documents. Function calling extends these capabilities further by enabling interaction with external tools and services.
Start building advanced AI applications with Nova models and Spring AI today!
# AWS Bedrock Prompt Caching Support in Spring AI
Source: https://springaicommunity.mintlify.app/blog/model-providers/bedrock-prompt-caching
In our [previous blog post about Anthropic prompt caching](https://spring.io/blog/2025/10/27/spring-ai-anthropic-prompt-caching-blog), we explored how prompt...
In our [previous blog post about Anthropic prompt caching](https://spring.io/blog/2025/10/27/spring-ai-anthropic-prompt-caching-blog), we explored how prompt caching dramatically reduces API costs and latency by reusing previously processed prompt content. We introduced Spring AI's five strategic caching patterns for Anthropic Claude models and showed how they automatically handle cache breakpoint placement while respecting the 4-breakpoint limit.
AWS Bedrock brings prompt caching to a broader ecosystem—supporting both Claude models (accessed via Bedrock) and Amazon's own Nova family. If you're considering Bedrock or already using it, you'll find the same Spring AI caching strategies apply with a few key differences.
In this blog post, we explain what's different about prompt caching in AWS Bedrock compared to Anthropic's direct API and how Spring AI maintains consistent patterns across both providers.
## What AWS Bedrock Adds
AWS Bedrock extends prompt caching beyond Claude to include Amazon Nova models:
**Claude models** (via Bedrock):
* Claude 3 Opus 4.1, Opus 4, Sonnet 4.5, Sonnet 4, Haiku 4.5
* Claude 3.7 Sonnet, 3.5 Haiku
* Full caching support including tool definitions
**Amazon Nova models**:
* Nova Micro, Lite, Pro, Premier
* System and conversation caching support only
For complete model details, see [AWS Bedrock supported models](https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-caching.html#prompt-caching-models).
## Key Differences from Anthropic Direct API
While the core caching concepts remain the same (as covered in our [previous blog](https://spring.io/blog/2025/10/27/spring-ai-anthropic-prompt-caching-blog)), AWS Bedrock has several differences worth understanding.
### Fixed 5-Minute Cache TTL
AWS Bedrock uses a fixed 5-minute TTL with no configuration options, while Anthropic's direct API offers optional 1-hour caching.
```java theme={null}
// Anthropic direct API: optional TTL configuration
AnthropicCacheOptions.builder()
.strategy(AnthropicCacheStrategy.SYSTEM_ONLY)
.messageTypeTtl(MessageType.SYSTEM, AnthropicCacheTtl.ONE_HOUR)
.build()
// AWS Bedrock: always 5 minutes
BedrockCacheOptions.builder()
.strategy(BedrockCacheStrategy.SYSTEM_ONLY)
.build()
```
For high-frequency workloads (requests every few seconds or minutes), the 5-minute TTL keeps the cache warm. For applications with requests spaced 5-30 minutes apart, cache entries may expire between requests.
### Tool Caching Not Supported on Nova
Amazon Nova models do not support tool caching. Attempting to use `TOOLS_ONLY` or `SYSTEM_AND_TOOLS` strategies with Nova throws an exception.
```java theme={null}
// Works with Claude models
BedrockChatOptions.builder()
.model("anthropic.claude-sonnet-4-5-20250929-v1:0")
.cacheOptions(BedrockCacheOptions.builder()
.strategy(BedrockCacheStrategy.SYSTEM_AND_TOOLS)
.build())
.toolCallbacks(tools)
.build()
// Throws exception with Nova models
BedrockChatOptions.builder()
.model("us.amazon.nova-pro-v1:0")
.cacheOptions(BedrockCacheOptions.builder()
.strategy(BedrockCacheStrategy.TOOLS_ONLY)
.build())
.toolCallbacks(tools)
.build()
// Use SYSTEM_ONLY for Nova
BedrockChatOptions.builder()
.model("us.amazon.nova-pro-v1:0")
.cacheOptions(BedrockCacheOptions.builder()
.strategy(BedrockCacheStrategy.SYSTEM_ONLY)
.build())
.build()
```
### Model-Specific Token Thresholds
| Model | Minimum Tokens per Checkpoint |
| -------------------------------------------------------------- | ----------------------------- |
| Claude 3.7 Sonnet, 3.5 Sonnet v2, Opus 4, Sonnet 4, Sonnet 4.5 | 1,024 |
| Claude 3.5 Haiku | 2,048 |
| Claude Haiku 4.5 | 4,096 |
| Amazon Nova (all variants) | 1,000 |
See [Bedrock token limits documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-caching.html#prompt-caching-models) for details.
### Cache Metrics Naming
| Metric | Anthropic Direct | AWS Bedrock |
| ---------------------- | -------------------------- | ----------------------- |
| Creating a cache entry | `cacheCreationInputTokens` | `cacheWriteInputTokens` |
| Reading from cache | `cacheReadInputTokens` | `cacheReadInputTokens` |
## Same Spring AI Patterns Across Providers
Spring AI uses identical caching strategies across both providers:
```java theme={null}
BedrockCacheStrategy.SYSTEM_ONLY ←→ AnthropicCacheStrategy.SYSTEM_ONLY
BedrockCacheStrategy.TOOLS_ONLY ←→ AnthropicCacheStrategy.TOOLS_ONLY
BedrockCacheStrategy.SYSTEM_AND_TOOLS ←→ AnthropicCacheStrategy.SYSTEM_AND_TOOLS
BedrockCacheStrategy.CONVERSATION_HISTORY ←→ AnthropicCacheStrategy.CONVERSATION_HISTORY
```
Let's take a look at how similar the code is:
```java theme={null}
// Anthropic direct API
ChatResponse response = anthropicChatModel.call(
new Prompt(
List.of(new SystemMessage(systemPrompt), new UserMessage(userQuery)),
AnthropicChatOptions.builder()
.model(AnthropicApi.ChatModel.CLAUDE_4_5_SONNET)
.cacheOptions(AnthropicCacheOptions.builder()
.strategy(AnthropicCacheStrategy.SYSTEM_ONLY)
.build())
.maxTokens(500)
.build()
)
);
// AWS Bedrock (nearly identical)
ChatResponse response = bedrockChatModel.call(
new Prompt(
List.of(new SystemMessage(systemPrompt), new UserMessage(userQuery)),
BedrockChatOptions.builder()
.model("anthropic.claude-sonnet-4-5-20250929-v1:0")
.cacheOptions(BedrockCacheOptions.builder()
.strategy(BedrockCacheStrategy.SYSTEM_ONLY)
.build())
.maxTokens(500)
.build()
)
);
```
The only differences: the chat model instance, options class, and model identifier format.
## Provider Characteristics at a Glance
| Feature | AWS Bedrock | Anthropic Direct |
| ----------------- | ----------------------------------------------- | -------------------------------------------------- |
| **Cache TTL** | 5 minutes (fixed) | 5 minutes (default), 1 hour (optional) |
| **Models** | Claude + Nova | Claude only |
| **Tool Caching** | Claude only | All Claude models |
| **Token Metrics** | `cacheWriteInputTokens`, `cacheReadInputTokens` | `cacheCreationInputTokens`, `cacheReadInputTokens` |
| **Pricing** | Varies by region/model | Published per-model |
| **Cost Pattern** | \~25% write premium, \~90% read savings | 25% write premium, 90% read savings |
## Example: Document Analysis with Caching
Here's a practical example showing cache effectiveness:
```java theme={null}
@Service
public class ContractAnalyzer {
private final BedrockProxyChatModel chatModel;
private final DocumentExtractor documentExtractor;
public AnalysisReport analyzeContract(String contractId) {
String contractText = documentExtractor.extract(contractId);
String systemPrompt = """
You are an expert legal analyst specializing in commercial contracts.
Analyze the following contract and provide precise insights about
terms, obligations, risks, and opportunities:
CONTRACT:
%s
""".formatted(contractText);
String[] questions = {
"What are the key legal clauses and penalties?",
"Summarize the payment terms and financial obligations.",
"What intellectual property rights are defined?",
"Identify potential compliance risks.",
"What are the performance milestones?"
};
AnalysisReport report = new AnalysisReport();
BedrockChatOptions options = BedrockChatOptions.builder()
.model("anthropic.claude-sonnet-4-5-20250929-v1:0")
.cacheOptions(BedrockCacheOptions.builder()
.strategy(BedrockCacheStrategy.SYSTEM_ONLY)
.build())
.maxTokens(1000)
.build();
for (int i = 0; i < questions.length; i++) {
ChatResponse response = chatModel.call(
new Prompt(
List.of(new SystemMessage(systemPrompt), new UserMessage(questions[i])),
options
)
);
report.addSection(questions[i], response.getResult().getOutput().getText());
logCacheMetrics(response, i);
}
return report;
}
private void logCacheMetrics(ChatResponse response, int questionNum) {
Integer cacheWrite = (Integer) response.getMetadata()
.getMetadata().get("cacheWriteInputTokens");
Integer cacheRead = (Integer) response.getMetadata()
.getMetadata().get("cacheReadInputTokens");
if (questionNum == 0 && cacheWrite != null) {
logger.info("Cache created: {} tokens", cacheWrite);
} else if (cacheRead != null && cacheRead > 0) {
logger.info("Cache hit: {} tokens", cacheRead);
}
}
}
```
AWS Bedrock provides cache metrics through the response metadata:
First request: `cacheWriteInputTokens` > 0, `cacheReadInputTokens` = 0
Subsequent requests (within TTL): `cacheWriteInputTokens` = 0, `cacheReadInputTokens` > 0
With a 3,500-token system prompt, this yields approximately 65% cost reduction on cached content (first question pays \~1.25x, subsequent questions pay \~0.10x).
## Using Different Models on Bedrock
```java theme={null}
@Service
public class MultiModelService {
// Nova: System prompt caching
public String analyzeWithNova(String document, String query) {
return chatClient.prompt()
.system("You are an expert analyst. Context: " + document)
.user(query)
.options(BedrockChatOptions.builder()
.model("us.amazon.nova-pro-v1:0")
.cacheOptions(BedrockCacheOptions.builder()
.strategy(BedrockCacheStrategy.SYSTEM_ONLY)
.build())
.maxTokens(500)
.build())
.call()
.content();
}
// Claude: System + tool caching
public String analyzeWithTools(String document, String query,
List tools) {
return chatClient.prompt()
.system("You are an expert analyst. Context: " + document)
.user(query)
.options(BedrockChatOptions.builder()
.model("anthropic.claude-sonnet-4-5-20250929-v1:0")
.cacheOptions(BedrockCacheOptions.builder()
.strategy(BedrockCacheStrategy.SYSTEM_AND_TOOLS)
.build())
.toolCallbacks(tools)
.maxTokens(500)
.build())
.call()
.content();
}
}
```
## Getting Started
Add the Spring AI Bedrock Converse starter:
**Note:** AWS Bedrock Prompt caching support is available in Spring AI `1.1.0` and later. Try it with the latest `1.1.0-SNAPSHOT` version.
```xml theme={null}
org.springframework.ai
spring-ai-starter-model-bedrock-converse
```
Configure AWS credentials:
```properties theme={null}
spring.ai.bedrock.aws.region=us-east-1
spring.ai.bedrock.aws.access-key=${AWS_ACCESS_KEY_ID}
spring.ai.bedrock.aws.secret-key=${AWS_SECRET_ACCESS_KEY}
```
You can then start using prompt caching via AWS Bedrock as shown in the examples above.
## Strategy Selection Reference
| Strategy | Use When | Claude | Nova |
| ---------------------- | ---------------------------------- | ------ | ---- |
| `SYSTEM_ONLY` | Large stable system prompt | Yes | Yes |
| `TOOLS_ONLY` | Large stable tools, dynamic system | Yes | No |
| `SYSTEM_AND_TOOLS` | Both large and stable | Yes | No |
| `CONVERSATION_HISTORY` | Multi-turn conversations | Yes | Yes |
| `NONE` | Disable caching explicitly | Yes | Yes |
For detailed strategy explanations, cache hierarchy, and cascade invalidation patterns, see our [Anthropic prompt caching blog post](https://spring.io/blog/2025/10/27/spring-ai-anthropic-prompt-caching-blog). These concepts still hold true in the case of AWS Bedrock.
## Conclusion
AWS Bedrock extends prompt caching to Amazon Nova models while maintaining full support for Claude models. The key differences from Anthropic's direct API are a fixed 5-minute TTL, Nova's lack of tool caching support, and region-specific pricing.
Spring AI provides the same strategic caching patterns across both providers. Whether you choose Claude through Anthropic, Claude through Bedrock, or Amazon Nova models, the five caching strategies work consistently with minimal code changes.
The decision between providers depends on model availability (Nova is only on Bedrock), cache TTL requirements, and tool caching needs (Nova doesn't support it).
For more on prompt caching support in Spring AI for AWS Bercok see [Spring AI Bedrock documentation](https://docs.spring.io/spring-ai/reference/1.1-SNAPSHOT/api/chat/bedrock-converse.html#_prompt_caching) and [AWS Bedrock Prompt Caching documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-caching.html).
# Spring AI with Docker Model Runner
Source: https://springaicommunity.mintlify.app/blog/model-providers/docker-model-runner
> This blog post is authored by [Eddú Meléndez](https://github.com/eddumelendez).
> This blog post is authored by [Eddú Meléndez](https://github.com/eddumelendez).
Docker recently [released a Model Runner in Docker Desktop for Mac 4.40.0](https://www.docker.com/blog/docker-desktop-4-40/) on Apple silicon. The Docker Model Runner provides a local Inference API designed to be compatible with the OpenAI API, enabling easy integration with [Spring AI](https://docs.spring.io/spring-ai/reference/api/chat/dmr-chat.html) as part of the Spring AI 1.0.0-M7 release. Models are distributed as standard OCI artifacts on Docker Hub under the [ai namespace](https://hub.docker.com/u/ai).
## Prerequisites
* Download Docker Desktop for Mac 4.40.0.
* Choose one of the following options to enable the Model Runner:
Option 1:
* Enable Model Runner \`docker desktop enable model-runner --tcp 12434\`.
* Set the base-url to \`[http://localhost:12434/engines\\\`](http://localhost:12434/engines\\`)
Option 2:
* Enable Model Runner \`docker desktop enable model-runner\`.
* Use [Testcontainers](https://testcontainers.com/) and set the base-url as follows:
```java theme={null}
@Container
private static final SocatContainer socat = new SocatContainer().withTarget(80, "model-runner.docker.internal");
@Bean
public OpenAiApi chatCompletionApi() {
var baseUrl = "http://%s:%d/engines".formatted(socat.getHost(), socat.getMappedPort(80));
return OpenAiApi.builder().baseUrl(baseUrl).apiKey("test").build();
}
```
Next, pull the model \`docker model pull ai/gemma3\` and confirm it is available locally \`docker model list\`
## Dependencies
Go to start.spring.io, select Spring Web, OpenAI and Testcontainers and generate the project.
The following dependencies must be listed
```
org.springframework.boot
spring-boot-starter-web
org.springframework.ai
spring-ai-openai-spring-boot-starter
org.springframework.ai
spring-ai-spring-boot-testcontainers
test
```
Also, make sure the Spring AI BOM is present
```
org.springframework.ai
spring-ai-bom
${spring-ai.version}
pom
import
```
Configuring Spring AI
To use Docker Model Runner, we need to configure the OpenAI client to point to the right endpoint and use the model pulled earlier
For Option 1: Let’s configure the src/main/resources/application.properties
```
spring.ai.openai.api-key=ignored
spring.ai.openai.base-url=http://localhost:12434/engines
spring.ai.openai.chat.options.model=ai/gemma3
```
For Option 2 (Using Testcontainers): Let’s go to \`TestcontainersConfiguration\`, define the SocatContainer bean and register the properties with \`DynamicPropertyRegistrar\` bean.
```java theme={null}
@TestConfiguration(proxyBeanMethods = false)
class TestcontainersConfiguration {
@Bean
SocatContainer socat() {
return new SocatContainer(DockerImageName.parse("alpine/socat:1.8.0.1"))
.withTarget(80, "model-runner.docker.internal");
}
@Bean
DynamicPropertyRegistrar properties(SocatContainer socat) {
return (registrar) -> {
registrar.add("spring.ai.openai.base-url", () -> "http://%s:%d/engines".formatted(socat.getHost(), socat.getMappedPort(80)));
registrar.add("spring.ai.openai.api-key", () -> "test-api-key");
registrar.add("spring.ai.openai.chat.options.model", () -> "ai/gemma3");
};
}
}
```
## Chat example
Now, let’s create a simple controller
```java theme={null}
@RestController
public class ChatController {
private final ChatClient chatClient;
public ChatController(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder.build();
}
@GetMapping("/chat")
public String chat(@RequestParam String message) {
return this.chatClient.prompt()
.user(message)
.call()
.content();
}
@GetMapping("/chat-stream")
public Flux chatStream(@RequestParam String message) {
return this.chatClient.prompt()
.user(message)
.stream()
.content();
}
}
```
Run the application with \`./mvnw spring-boot:test-run\`
Using [httpie](https://httpie.io/), let’s call to the \`/chat\` endpoint
```
http :8080/chat message=="tell me a joke"
```
We can also call to the \`/chat-stream\` endpoint
```
http :8080/chat-stream message=="tell me a haiku about docker containers"
```
## Tool example
Docker Model Runner of course supports tool calling if used with a model that supports tool calling.
Create a \`FunctionCallConfig\` class and add a simple function
```java theme={null}
@Configuration(proxyBeanMethods = false)
class FunctionCallConfig {
@Bean
@Description("Get the stock price")
public Function stockFunction() {
return new MockStockService();
}
static class MockStockService implements Function {
public record StockRequest(String symbol) {}
public record StockResponse(double price) {}
@Override
public StockResponse apply(StockRequest request) {
double price = request.symbol().contains("AAPL") ? 198 : 114;
return new StockResponse(price);
}
}
}
```
Now, let’s register \`stockFunction\` function
```java theme={null}
@GetMapping("/stocks")
public String stocks(@RequestParam String message) {
return this.chatClient.prompt()
.user(message)
.tools("stockFunction")
.call()
.content();
}
```
Run the application \`./mvnw spring-boot:test-run\` and call the \`/stocks\` endpoint
```
http :8080/stocks message=="What's AAPL and NVDA stock price?"
```
The response should be something like \`AAPL stock price is 198.0 and NVDA stock price is 114.0.\` based on the hardcoded values we set.
## References
* # Introducing Docker Model Runner [https://www.docker.com/blog/introducing-docker-model-runner/](https://www.docker.com/blog/introducing-docker-model-runner/)
* Run LLMs Locally with Docker: A Quickstart Guide to Model Runner [https://www.docker.com/blog/run-llms-locally/](https://www.docker.com/blog/run-llms-locally/)
* Docker Model Runner docs [https://docs.docker.com/desktop/features/model-runner/](https://docs.docker.com/desktop/features/model-runner/)
* Spring AI Docker Model Runner Example [https://github.com/eddumelendez/spring-ai-dmr](https://github.com/eddumelendez/spring-ai-dmr)
## Conclusion
Docker Model Runner allows you to iterate faster, stay local and access an OpenAI-compatible API. It streamlines the development experience by enabling seamless integration with Spring AI’s OpenAI module, letting developers stay within their familiar inner-loop tooling. This empowers teams to build and test AI applications at their own pace—locally, securely, and efficiently. In the future, integration with Testcontainers will make it even easier to pull and run models on demand, further simplifying setup and testing workflows.Docker Model Runner allows you to iterate faster, stay local and access an OpenAI-compatible API. It streamlines the development experience by enabling seamless integration with Spring AI’s OpenAI module, letting developers stay within their familiar inner-loop tooling. This empowers teams to build and test AI applications at their own pace—locally, securely, and efficiently.
# Spring AI with Groq - a blazingly fast AI inference engine
Source: https://springaicommunity.mintlify.app/blog/model-providers/groq
> Faster information processing not only informs - it transforms how we perceive and innovate.
> Faster information processing not only informs - it transforms how we perceive and innovate.
[Spring AI](https://docs.spring.io/spring-ai/reference/), a powerful framework for integrating AI capabilities into Spring applications, now offers support for [Groq](https://groq.com/) - a blazingly fast AI inference engine with support for Tool/Function calling.
Leveraging Groq's OpenAI-compatible API, Spring AI seamlessly integrates by adapting its existing [OpenAI Chat](https://docs.spring.io/spring-ai/reference/api/chat/openai-chat.html) client.
This approach enables developers to harness Groq's high-performance models through the familiar Spring AI API.

We'll explore how to configure and use the Spring AI OpenAI chat client to connect with Groq.
For detailed information, consult the Spring AI [Groq documentation](https://docs.spring.io/spring-ai/reference/api/chat/groq-chat.html) and related [tests](https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/proxy/GroqWithOpenAiChatModelIT.java).
# Groq API Key
To interact with Groq, you'll need to obtain a Groq API key from [https://console.groq.com/keys](https://console.groq.com/keys).
# Dependencies
Add the Spring AI OpenAI starter to your project.
``
``org.springframework.ai``
``spring-ai-openai-spring-boot-starter``
``
For Gradle, add this to your `build.gradle`
dependencies \{
implementation 'org.springframework.ai:spring-ai-openai-spring-boot-starter'
}
Ensure you've added the Spring [Milestone and Snapshot repositories](https://docs.spring.io/spring-ai/reference/getting-started.html#repositories) and add the [Spring AI BOM](https://docs.spring.io/spring-ai/reference/getting-started.html#dependency-management).
# Configuring Spring AI for Groq
To use Groq with Spring AI, we need to configure the OpenAI client to point to Groq's API endpoint and use Groq-specific models.
Add the following environment variables to your project:
export SPRING\_AI\_OPENAI\_API\_KEY=``\
export SPRING\_AI\_OPENAI\_BASE\_URL=[https://api.groq.com/openai](https://api.groq.com/openai)\
export SPRING\_AI\_OPENAI\_CHAT\_OPTIONS\_MODEL=llama3-70b-8192
Alternatively, you can add these to your `application.properties` file:
spring.ai.openai.api-key=``
spring.ai.openai.base-url=[https://api.groq.com/openai](https://api.groq.com/openai)
spring.ai.openai.chat.options.model=llama3-70b-8192
spring.ai.openai.chat.options.temperature=0.7
Key points:
* The `api-key` is set to one of your [Groq keys](https://console.groq.com/keys).
* The `base-url` is set to Groq's API endpoint: `https://api.groq.com/openai`
* The `model` is set to one of Groq's available [Models](https://console.groq.com/docs/models).
For the complete list of configuration properties, consult the [Groq chat properties](https://docs.spring.io/spring-ai/reference/api/chat/groq-chat.html#_chat_properties) documentation.
# Code Example
Now that we've configured Spring AI to use Groq, let's look at a simple example of how to use it in your application.
@RestController
public class ChatController \{
private final ChatClient chatClient;
@Autowired
public ChatController(ChatClient.Builder builder) \{
this.chatClient = builder.build();
}
@GetMapping("/ai/generate")
public Map generate(@RequestParam(value = "message", defaultValue = "Tell me a joke") String message) \{
String response = chatClient.prompt().user(message).call().content();
return Map.of("generation", response);
}
@GetMapping("/ai/generateStream")
public Flux`` generateStream(@RequestParam(value = "message",
defaultValue = "Tell me a joke") String message) \{
return chatClient.prompt().user(message).stream().content();
}
}
In this example, we've created a simple REST controller with two endpoints:
* `/ai/generate`: Generates a single response to a given prompt.
* `/ai/generateStream`: Streams the response, which can be useful for longer outputs or real-time interactions.
# Tools/Functions
Groq API endpoints support [tool/function calling](https://console.groq.com/docs/tool-use) when selecting one of the Tool/Function supporting models.

You can register custom Java functions with your ChatModel and have the provided Groq model intelligently choose to output a JSON object containing arguments to call one or many of the registered functions.
This is a powerful technique to connect the LLM capabilities with external tools and APIs.
### Tool Example
Here's a simple example of how to use Groq function calling with Spring AI:
@SpringBootApplication
public class GroqApplication \{
public static void main(String\[] args) \{
SpringApplication.run(GroqApplication.class, args);
}
@Bean
CommandLineRunner runner(ChatClient.Builder chatClientBuilder) \{
return args -> \{
var chatClient = chatClientBuilder.build();
var response = chatClient.prompt()
.user("What is the weather in Amsterdam and Paris?")
.functions("weatherFunction") // reference by bean name.
.call()
.content();
System.out.println(response);
};
}
@Bean
@Description("Get the weather in location")
public Function`` weatherFunction() \{
return new MockWeatherService();
}
public static class MockWeatherService implements Function`` \{
public record WeatherRequest(String location, String unit) \{}
public record WeatherResponse(double temp, String unit) \{}
@Override
public WeatherResponse apply(WeatherRequest request) \{
double temperature = request.location().contains("Amsterdam") ? 20 : 25;
return new WeatherResponse(temperature, request.unit);
}
}
}
In this example, when the model needs weather information, it will automatically call the `weatherFunction` bean, which can then fetch real-time weather data.
The expected response looks like this: "The weather in Amsterdam is currently 20 degrees Celsius, and the weather in Paris is currently 25 degrees Celsius."
Read more about OpenAI [Function Calling](https://docs.spring.io/spring-ai/reference/api/chat/functions/openai-chat-functions.html).
# Key Considerations
When using Groq with Spring AI, keep the following points in mind:
* Tool/Function Calling: Groq [supports](https://console.groq.com/docs/tool-use) Tool/Function calling. Check for the recommended models to use.
* API Compatibility: The Groq API is not fully compatible with the OpenAI API. Be aware of potential differences in behavior or features.
* Model Selection: Ensure you're using one of the Groq-specific [models](https://console.groq.com/docs/models).
* Multimodal Limitations: Currently, Groq doesn't support multimodal messages.
* Performance: Groq is known for its fast inference times. You may notice improved response speeds compared to other providers, especially for larger models.
# Conclusion
Integrating Groq with Spring AI opens up new possibilities for developers looking to leverage high-performance AI models in their Spring applications.
By repurposing the OpenAI client, Spring AI makes it straightforward to switch between different AI providers, allowing you to choose the best solution for your specific needs.
As you explore this integration, remember to stay updated with the latest documentation from both [Spring AI](https://docs.spring.io/spring-ai/reference/index.html) and [Groq](https://console.groq.com/docs/quickstart), as features and compatibility may evolve over time.
We encourage you to experiment with different Groq models and compare their performance and outputs to find the best fit for your use case.
Happy coding, and enjoy the speed and capabilities that Groq brings to your AI-powered Spring applications!
# Spring AI with NVIDIA LLM API
Source: https://springaicommunity.mintlify.app/blog/model-providers/nvidia
Spring AI now supports [NVIDIA's Large Language Model API](https://docs.api.nvidia.com/nim/reference/llm-apis), offering integration with a wide range of...
Spring AI now supports [NVIDIA's Large Language Model API](https://docs.api.nvidia.com/nim/reference/llm-apis), offering integration with a wide range of [models](https://docs.api.nvidia.com/nim/reference/llm-apis#models). By leveraging NVIDIA's OpenAI-compatible API, Spring AI allows developers to use NVIDIA's LLMs through the familiar [Spring AI API](https://docs.spring.io/spring-ai/reference/api/chat/nvidia-chat.html).

We'll explore how to configure and use the Spring AI OpenAI chat client to connect with NVIDIA LLM API.
* The demo application code is available in the [nvidia-llm](https://github.com/tzolov/nvidia-llm) GitHub repository.
* The SpringAI / NVIDIA integration [documentation](https://docs.spring.io/spring-ai/reference/api/chat/nvidia-chat.html).
# Prerequisite
* Create [NVIDIA](https://build.nvidia.com/explore/discover) account with sufficient credits.
* Select your preferred [LLM model](https://docs.api.nvidia.com/nim/reference/llm-apis#models) from NVIDIA's offerings. Like the `meta/llama-3.1-70b-instruct` in the screenshot below.
* From the model's page, obtain the API key for your chosen model.

# Dependencies
To get started, add the Spring AI OpenAI starter to your project.
For Maven, add this to your pom.xml:
``
``org.springframework.ai``
``spring-ai-openai-spring-boot-starter``
``
For Gradle, add this to your build.gradle:
gradleCopydependencies \{
implementation 'org.springframework.ai:spring-ai-openai-spring-boot-starter'
}
Ensure you've added the Spring Milestone and Snapshot repositories and add the [Spring AI BOM](https://docs.spring.io/spring-ai/reference/getting-started.html#dependency-management).
# Configuring Spring AI
To use NVIDIA LLM API with Spring AI, we need to configure the OpenAI client to point to the NVIDIA LLM API endpoint and use NVIDIA-specific models.
Add the following environment variables to your project:
export SPRING\_AI\_OPENAI\_API\_KEY=``
export SPRING\_AI\_OPENAI\_BASE\_URL=[https://integrate.api.nvidia.com](https://integrate.api.nvidia.com)
export SPRING\_AI\_OPENAI\_CHAT\_OPTIONS\_MODEL=meta/llama-3.1-70b-instruct
export SPRING\_AI\_OPENAI\_EMBEDDING\_ENABLED=false
export SPRING\_AI\_OPENAI\_CHAT\_OPTIONS\_MAX\_TOKENS=2048
Alternatively, you can add these to your application.properties file:
spring.ai.openai.api-key=``
spring.ai.openai.base-url=[https://integrate.api.nvidia.com](https://integrate.api.nvidia.com)
spring.ai.openai.chat.options.model=meta/llama-3.1-70b-instruct
# The NVIDIA LLM API doesn't support embeddings.
spring.ai.openai.embedding.enabled=false
# The NVIDIA LLM API requires this parameter to be set explicitly or error will be thrown.
spring.ai.openai.chat.options.max-tokens=2048
Key points:
* The `api-key` is set to your NVIDIA API key.
* The `base-url` is set to NVIDIA's LLM API endpoint: [https://integrate.api.nvidia.com](https://integrate.api.nvidia.com)
* The `model` is set to one of the models available on NVIDIA's LLM API.
* The NVIDIA LLM API reuquires the `max-tokens` to be explicitly set or a server error will be thrown.
* Since the NVIDIA LLM API is LLM only we can disable the embedding endpong: `embedding.enabled=false`.
Check the reference documentation for the complete list of [configuration properties](https://docs.spring.io/spring-ai/reference/api/chat/nvidia-chat.html#_chat_properties).
# Code Example
Now that we've configured Spring AI to use NVIDIA LLM API, let's look at a simple example of how to use it in your application.
@RestController
public class ChatController \{
private final ChatClient chatClient;
@Autowired
public ChatController(ChatClient.Builder builder) \{
this.chatClient = builder.build();
}
@GetMapping("/ai/generate")
public String generate(@RequestParam(value = "message", defaultValue = "Tell me a joke") String message) \{
return chatClient.prompt().user(message).call().content();
}
@GetMapping("/ai/generateStream")
public Flux`` generateStream(
@RequestParam(value = "message", defaultValue = "Tell me a joke") String message) \{
return chatClient.prompt().user(message).stream().content();
}
}
In the [ChatController.java](https://github.com/tzolov/nvidia-llm/blob/main/src/main/java/com/example/nvidia/ChatController.java) example, we've created a simple REST controller with two endpoints:
* `/ai/generate`: Generates a single response to a given prompt.
* `/ai/generateStream`: Streams the response, which can be useful for longer outputs or real-time interactions.
# Tool/Function Calling
NVIDIA LLM API endpoints support tool/function calling when selecting one of the Tool/Function supporting models.

You can register custom Java functions with your ChatModel and have the provided LLM model intelligently choose to output a JSON object containing arguments to call one or many of the registered functions.
This is a powerful technique to connect the LLM capabilities with external tools and APIs.
Find more about SpringAI/OpenAI [Function Calling](https://docs.spring.io/spring-ai/reference/api/chat/functions/openai-chat-functions.html) support.
### Tool Example
Here's a simple example of how to use too/function calling with Spring AI:
@SpringBootApplication
public class NvidiaLlmApplication \{
public static void main(String\[] args) \{
SpringApplication.run(NvidiaLlmApplication.class, args);
}
@Bean
CommandLineRunner runner(ChatClient.Builder chatClientBuilder) \{
return args -> \{
var chatClient = chatClientBuilder.build();
var response = chatClient.prompt()
.user("What is the weather in Amsterdam and Paris?")
.functions("weatherFunction") // reference by bean name.
.call()
.content();
System.out.println(response);
};
}
@Bean
@Description("Get the weather in location")
public Function`` weatherFunction() \{
return new MockWeatherService();
}
public static class MockWeatherService implements Function`` \{
public record WeatherRequest(String location, String unit) \{}
public record WeatherResponse(double temp, String unit) \{}
@Override
public WeatherResponse apply(WeatherRequest request) \{
double temperature = request.location().contains("Amsterdam") ? 20 : 25;
return new WeatherResponse(temperature, request.unit);
}
}
}
In the [NvidiaLlmApplication.java](https://github.com/tzolov/nvidia-llm/blob/main/src/main/java/com/example/nvidia/NvidiaLlmApplication.java) example, when the model needs weather information, it will automatically call the `weatherFunction` bean, which can then fetch real-time weather data.
The expected response looks like this:
> The weather in Amsterdam is currently 20 degrees Celsius, and the weather in Paris is currently 25 degrees Celsius.
# Key Considerations
When using NVIDIA LLM API with Spring AI, keep the following points in mind:
* **Model Selection**: NVIDIA offers a wide range of models from various providers. Choose the appropriate model for your use case.
* **API Compatibility**: The NVIDIA LLM API is designed to be compatible with the OpenAI API, which allows for easy integration with Spring AI.
* **Performance**: NVIDIA's LLM API is optimized for high-performance inference. You may notice improved response speeds, especially for larger models.
* **Specialized Models**: NVIDIA offers models specialized for different tasks, such as code completion, math problems, and general chat. Select the most appropriate model for your specific needs.
* **API Limits**: Be aware of any rate limits or usage quotas associated with your NVIDIA API key.
# References
For further information check the Spring AI and OpenAI reference documentations.
* [Spring AI NVIDIA Reference docs](https://docs.spring.io/spring-ai/reference/api/chat/nvidia-chat.html)
* [NVIDIA LLM API](https://docs.api.nvidia.com/nim/reference/llm-apis#overview)
* [Spring AI - NVIDIA - Demo](https://github.com/tzolov/nvidia-llm/blob/main/src/main/java/com/example/nvidia/ChatController.java)
* Previous Spring AI blog posts:
* [Spring AI Embraces OpenAI's Structured Outputs: Enhancing JSON Response Reliability](https://spring.io/blog/2024/08/09/spring-ai-embraces-openais-structured-outputs-enhancing-json-response)
* [Spring AI with Groq - a blazingly fast AI inference engine](https://spring.io/blog/2024/07/31/spring-ai-with-groq-a-blazingly-fast-ai-inference-engine)
* [Spring AI with Ollama Tool Support](https://spring.io/blog/2024/07/26/spring-ai-with-ollama-tool-support)
* [Spring AI - Structured Output](https://spring.io/blog/2024/05/09/spring-ai-structured-output)
* [Spring AI - Multimodality - Orbis Sensualium Pictus](https://spring.io/blog/2024/04/19/spring-ai-multimodality-orbis-sensualium-pictus)
* [Function Calling in Java and Spring AI using the latest Mistral AI API](https://spring.io/blog/2024/03/06/function-calling-in-java-and-spring-ai-using-the-latest-mistral-ai-api)
* [Spring Cloud Function for Azure Function](https://spring.io/blog/2023/02/24/spring-cloud-function-for-azure-function)
# Conclusion
Integrating NVIDIA LLM API with Spring AI opens up new possibilities for developers looking to leverage high-performance AI models in their Spring applications.
By repurposing the OpenAI client, Spring AI makes it straightforward to switch between different AI providers, allowing you to choose the best solution for your specific needs.
As you explore this integration, remember to stay updated with the latest documentation from both Spring AI and NVIDIA LLM API, as features and model availability may evolve over time.
We encourage you to experiment with different models and compare their performance and outputs to find the best fit for your use case.
Happy coding, and enjoy the speed and capabilities that NVIDIA LLM API brings to your AI-powered Spring applications!
# Leverage the Power of 45k, free, Hugging Face Models with Spring AI and Ollama
Source: https://springaicommunity.mintlify.app/blog/model-providers/ollama-huggingface
> This blog post is co-authored by our great contributor [Thomas Vitale](https://www.linkedin.com/in/vitalethomas/).
> This blog post is co-authored by our great contributor [Thomas Vitale](https://www.linkedin.com/in/vitalethomas/).
**Ollama** now supports all [GGUF](https://github.com/ggerganov/ggml/blob/master/docs/gguf.md) models from **Hugging Face**, allowing access to over 45,000 community-created models through [Spring AI's Ollama](https://docs.spring.io/spring-ai/reference/api/chat/ollama-chat.html) integration, runnable locally.

We'll explore using this new feature with Spring AI. The Spring AI Ollama integration can automatically pull unavailable models for both chat completion and embedding models. This is useful when switching models or deploying to new environments.
## Setting Up Spring AI with Ollama
Install Ollama on your system: [https://ollama.com/download](https://ollama.com/download).
**Tip**: Spring AI also supports [running Ollama via Testcontainers](https://docs.spring.io/spring-ai/reference/api/testcontainers.html) or integrating with an external Ollama service via [Kubernetes Service Bindings](https://docs.spring.io/spring-ai/reference/api/cloud-bindings.html).
Follow the [dependency management](https://docs.spring.io/spring-ai/reference/getting-started.html#dependency-management) guide to add the Spring AI BOM and the Spring AI Ollama boot starter to your project's Maven `pom.xml` file or Gradle `build.gradle` files.
Maven:
```xml theme={null}
org.springframework.ai
spring-ai-ollama-spring-boot-starter
```
Gradle:
```groovy theme={null}
implementation 'org.springframework.ai:spring-ai-ollama-spring-boot-starter'
```
Add the following properties to your `application.properties` file:
```properties theme={null}
spring.ai.ollama.chat.options.model=hf.co/bartowski/gemma-2-2b-it-GGUF
spring.ai.ollama.init.pull-model-strategy=always
```
* **spring.ai.ollama.chat.options.model**: Specifies the Hugging Face GGUF model to use using theformat: `hf.co/{username}/{repository}`
* **spring.ai.ollama.init.pull-model-strategy=always**: Enables automatic model pulling at startup time. For production, you should pre-download the models to avoid delays: `ollama pull hf.co/bartowski/gemma-2-2b-it-GGUF`.
**Note**: [Auto-pulling](https://docs.spring.io/spring-ai/reference/api/chat/ollama-chat.html#auto-pulling-models) is available in Spring AI 1.0.0-SNAPSHOT and the upcoming M4 release. For M3, pre-download models (`ollama pull hf.co/{username}/{repository}`).
You can disable the embedding auto-configuration if not required: `spring.ai.ollama.embedding.enabled=false`.
Otherwise, Spring AI will pull the `mxbai-embed-large` embedding model if not available locally.
## Usage Example
Using the configured Hugging Face model with Spring AI is straightforward and not different from using any other Spring AI model provider.
Here's a simple example:
```java theme={null}
@Bean
public CommandLineRunner run(ChatClient.Builder builder) {
var chatClient = builder.build();
return args -> {
var response = chatClient
.prompt("Tell me a joke")
.call()
.content();
logger.info("Answer: " + response);
};
}
```
## References
* [Spring AI Ollama Chat](https://docs.spring.io/spring-ai/reference/api/chat/ollama-chat.html)
* [Spring AI Ollama Embedding](https://docs.spring.io/spring-ai/reference/api/embeddings/ollama-embeddings.html)
* [Companion sample project](https://github.com/tzolov/spring-ai-ollama-huggingface-demo)
## Conclusion
The integration of Ollama's support for Hugging Face GGUF models with Spring AI opens up a world of possibilities for developers.
We encourage you to explore the vast collection of models on Hugging Face and experiment with different models in your Spring AI projects. Whether you're building advanced natural language understanding systems, creative writing tools, or complex analytical applications, Spring AI and Ollama provide the flexibility to easily leverage these powerful models.
Remember to stay updated with the latest developments in Spring AI and Ollama, as this field is rapidly evolving. Happy coding!
# Audio Multimodality: Expanding AI Interaction with Spring AI and OpenAI
Source: https://springaicommunity.mintlify.app/blog/prompts-and-output/audio-modality
> This blog post is co-authored by our great contributor [Thomas Vitale](https://www.linkedin.com/in/vitalethomas/).
> This blog post is co-authored by our great contributor [Thomas Vitale](https://www.linkedin.com/in/vitalethomas/).
OpenAI provides specialized models for `speech-to-text` and `text-to-speech` conversion, recognized for their performance and cost-efficiency. Spring AI integrates these capabilities via [Voice-to-Text](https://docs.spring.io/spring-ai/reference/api/audio/transcriptions/openai-transcriptions.html) and [Text-to-Speech (TTS)](https://docs.spring.io/spring-ai/reference/api/audio/speech/openai-speech.html).
The new [Audio Generation](https://platform.openai.com/docs/guides/audio) feature (`gpt-4o-audio-preview`) goes further, enabling mixed input and output modalities. Audio inputs can contain richer data than text alone. Audio can convey nuanced information like tone and inflection, and together with the audio outputs it enables asynchronous `speech-to-speech` interactions.
Additionally, this new multimodality opens up possibilities for innovative applications, such as structured data extraction. Developers can extract structured information not just from simple text, but also from images and audio, building complex, structured objects seamlessly.
## Spring AI Audio Integrations
The Spring AI [Multimodality Message API](https://docs.spring.io/spring-ai/reference/api/multimodality.html) simplifies the integration of multimodal capabilities with various AI models.
Now it fully supports OpenAI’s [Audio Input](https://docs.spring.io/spring-ai/reference/api/chat/openai-chat.html#_audio) and [Audio Output](https://docs.spring.io/spring-ai/reference/api/chat/openai-chat.html#_output_audio) modalities, thanks in large part to community member [Thomas Vitale](https://www.linkedin.com/in/vitalethomas/), who contributed to this feature's development.
### Setup
Follow the [Spring AI-OpenAI](https://docs.spring.io/spring-ai/reference/api/chat/openai-chat.html) integration documentation to prepare your environment.
### Audio Input
OpenAI’s [User Message API](https://platform.openai.com/docs/api-reference/chat/create#chat-create-messages) accepts base64-encoded audio files within messages using the [Media](https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/chat/messages/Media.java) type. Supported formats include `audio/mp3` and `audio/wav`.
**Example: Adding audio to an input prompt:**
```java theme={null}
// Prepare the audio resource
var audioResource = new ClassPathResource("speech1.mp3");
// Create a user message with audio and send it to the chat model
String response = chatClient.prompt()
.user(u -> u.text("What is this recording about?")
.media(MimeTypeUtils.parseMimeType("audio/mp3"), audioResource))
.options(OpenAiChatOptions.builder()
.withModel(OpenAiApi.ChatModel.GPT_4_O_AUDIO_PREVIEW).build())
.call()
.content();
```
### Audio Output Generation
The OpenAI [Assistant Message API](https://platform.openai.com/docs/api-reference/chat/create#chat-create-messages) can return base64-encoded audio files using the `Media` type.
**Example: Generating audio output:**
```java theme={null}
// Generate an audio response
ChatResponse response = chatClient
.prompt("Tell me a joke about the Spring Framework")
.options(OpenAiChatOptions.builder()
.withModel(OpenAiApi.ChatModel.GPT_4_O_AUDIO_PREVIEW)
.withOutputModalities(List.of("text", "audio"))
.withOutputAudio(new AudioParameters(Voice.ALLOY, AudioResponseFormat.WAV))
.build())
.call()
.chatResponse();
// Access the audio transcript
String audioTranscript = response.getResult().getOutput().getContent();
// Retrieve the generated audio
byte[] generatedAudio = response.getResult().getOutput().getMedia().get(0).getDataAsByteArray();
```
To generate audio output, specify the audio modality in `OpenAiChatOptions`. Use the `AudioParameters` class to customize the voice and the audio format.
## [Voice ChatBot Demo](https://github.com/tzolov/voice-assistant-chatbot)
This example demonstrates building an interactive chatbot using Spring AI that supports input and output audio. It shows how AI can enhance user interaction with natural-sounding audio responses.
### Setup
Add the Spring AI OpenAI starter:
```xml theme={null}
org.springframework.ai
spring-ai-openai-spring-boot-starter
```
Configure the API key, model name, and output audio modality in `application.properties`:
```
spring.main.web-application-type=none
spring.ai.openai.api-key=${OPENAI_API_KEY}
spring.ai.openai.chat.options.model=gpt-4o-audio-preview
spring.ai.openai.chat.options.output-modalities=text,audio
spring.ai.openai.chat.options.output-audio.voice=ALLOY
spring.ai.openai.chat.options.output-audio.format=WAV
```
### Implementation
The Java implementation of the voice chatbot, detailed below, creates a conversational AI assistant using audio input and output. It leverages Spring AI's integration with OpenAI models to enable seamless interactions with users.
**VoiceAssistantApplication**
* `VoiceAssistantApplication` serves as the main application.
* The `CommandLineRunner` bean initializes the chatbot:
* The `ChatClient` is configured using the `systemPrompt` for contextual understanding and an in-memory chat memory for conversation history.
* The `Audio` utility is used to record voice input from the user and play back audio responses generated by the AI.
* **Chat Loop:** Inside the loop:
* **Voice Recording:** The `audio.startRecording()` and `audio.stopRecording()` methods handle the recording process, pausing for user input.
* **Processing AI Response:** The user message is sent to the AI model via `chatClient.prompt()`. The audio data is encapsulated in the `Media` object.
* **Response Handling:** The AI-generated response is retrieved as text and played back as audio using the `Audio.play()` method.
Refer to the following code snippet for the implementation:
```java theme={null}
@Bean
public CommandLineRunner chatBot(ChatClient.Builder chatClientBuilder,
@Value("${chatbot.prompt:classpath:/marvin.paranoid.android.txt}") Resource systemPrompt) {
return args -> {
var chatClient = chatClientBuilder.defaultSystem(systemPrompt)
.defaultAdvisors(new MessageChatMemoryAdvisor(new InMemoryChatMemory()))
.build();
try (Scanner scanner = new Scanner(System.in)) {
Audio audio = new Audio();
while (true) {
audio.startRecording();
System.out.print("Recording your question ... press to stop! ");
scanner.nextLine();
audio.stopRecording();
System.out.print("PROCESSING ... ");
AssistantMessage response = chatClient.prompt()
.messages(new UserMessage("Please answer the questions in the audio input",
new Media(MediaType.parseMediaType("audio/wav"),
new ByteArrayResource(audio.getLastRecording()))))
.call()
.chatResponse()
.getResult()
.getOutput();
System.out.println("ASSISTANT: " + response.getContent());
Audio.play(response.getMedia().get(0).getDataAsByteArray());
}
}
};
}
```
The `Audio` utility, for capturing and playing back audio, is a single class leveraging the plain `Java Sound API`.
```
▗▄▄▖▗▄▄▖ ▗▄▄▖ ▗▄▄▄▖▗▖ ▗▖ ▗▄▄▖ ▗▄▖ ▗▄▄▄▖
▐▌ ▐▌ ▐▌▐▌ ▐▌ █ ▐▛▚▖▐▌▐▌ ▐▌ ▐▌ █
▝▀▚▖▐▛▀▘ ▐▛▀▚▖ █ ▐▌ ▝▜▌▐▌▝▜▌ ▐▛▀▜▌ █
▗▄▄▞▘▐▌ ▐▌ ▐▌▗▄█▄▖▐▌ ▐▌▝▚▄▞▘ ▐▌ ▐▌▗▄█▄▖
▗▄▄▖ ▗▄▖ ▗▄▄▖ ▗▄▖ ▗▖ ▗▖ ▗▄▖ ▗▄▄▄▖▗▄▄▄ ▗▄▖ ▗▖ ▗▖▗▄▄▄ ▗▄▄▖ ▗▄▖ ▗▄▄▄▖▗▄▄▄
▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌▐▛▚▖▐▌▐▌ ▐▌ █ ▐▌ █ ▐▌ ▐▌▐▛▚▖▐▌▐▌ █▐▌ ▐▌▐▌ ▐▌ █ ▐▌ █
▐▛▀▘ ▐▛▀▜▌▐▛▀▚▖▐▛▀▜▌▐▌ ▝▜▌▐▌ ▐▌ █ ▐▌ █ ▐▛▀▜▌▐▌ ▝▜▌▐▌ █▐▛▀▚▖▐▌ ▐▌ █ ▐▌ █
▐▌ ▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌▝▚▄▞▘▗▄█▄▖▐▙▄▄▀ ▐▌ ▐▌▐▌ ▐▌▐▙▄▄▀▐▌ ▐▌▝▚▄▞▘▗▄█▄▖▐▙▄▄▀
2024-12-01T11:00:11.274+01:00 INFO 31297 --- [voice-assistant-chatbot] [ main] s.a.d.a.m.VoiceAssistantApplication : Started VoiceAssistantApplication in 0.827 seconds (process running for 1.054)
Recording your question ... press to stop!
```
The complete demo is available on GitHub: [voice-assistant-chatbot](https://github.com/tzolov/voice-assistant-chatbot)
## Important Considerations
* [One hour of](https://github.com/tzolov/voice-assistant-chatbot) audio input is roughly equivalent to 128k tokens.
* The model currently supports `modalities = ["text", "audio"]`.
* Future updates may offer more flexible modality controls.
## Conclusion
The `gpt-4o-audio-preview` model unlocks new possibilities for dynamic audio interactions, enabling developers to build rich, AI-powered audio applications.
*Disclaimer: API capabilities and features may evolve. Refer to the latest OpenAI and Spring AI documentation for updates.*
# Spring AI - Multimodality - Orbis Sensualium Pictus
Source: https://springaicommunity.mintlify.app/blog/prompts-and-output/multimodality
*__UPDATE__ 20.07.2024 : Update Message API hierarchy diagram and update the model names supporting multimodality*
***UPDATE** 20.07.2024 : Update Message API hierarchy diagram and update the model names supporting multimodality*
***UPDATE** 02.06.2024 : Add an additional code snippet showing how to use the new ChatClient API.*
Humans process knowledge, simultaneously across multiple modes of data inputs. The way we learn, our experiences are all multimodal. We don't have just vision, just audio and just text.
These foundational principles of learning were articulated by the father of modern education John Amos Comenius, in his work, "Orbis Sensualium Pictus", dating back to 1658.

> "All things that are naturally connected ought to be taught in combination"
Contrary to those principles, in the past, our approach to Machine Learning was often focused on specialised models tailored to process a single modality. For instance, we developed audio models for tasks like text-to-speech or speech-to-text, and computer vision models for tasks such as object detection and classification.
However, a new wave of multimodal large language models starts to emerge. Examples include OpenAI's GPT-4o, Google's Vertex AI Gemini Pro 1.5, Anthropic's Claude3, and open source offerings LLaVA and balklava are able to accept multiple inputs, including text images, audio and video and generate text responses by integrating these inputs.
The multimodal large language model (LLM) features **enable the models to process and generate text in conjunction with other modalities such as images, audio, or video.**
# Spring AI - Multimodality
Multimodality refers to a model’s ability to simultaneously understand and process information from various sources, including text, images, audio, and other data formats.
The Spring AI Message API provides all necessary abstractions to support multimodal LLMs.

The UserMessage’s **content** field is used as primarily text inputs, while the, optional, **media** field allows adding one or more additional content of different modalities such as images, audio and video. The MimeType specifies the modality type.
Depending on the used LLMs the Media's data field can be either encoded raw media content or an URI to the content.
***Note:** The media field is currently applicable only for user input messages, e.g. `UserMessage`.*
## Example
Lets for example take the following picture (*multimodal.test.png*) as an input and ask the LLM to explain what it sees in the picture.

For most multimodal LLMs, the Spring AI code would look something like this:
byte\[] imageData = new ClassPathResource("/multimodal.test.png").getContentAsByteArray();
var userMessage = new UserMessage(
"Explain what do you see in this picture?", // text content
List.of(new Media(MimeTypeUtils.IMAGE\_PNG, imageData))); // image content
ChatResponse response = chatModel.call(new Prompt(List.of(userMessage)));
or with the new fluent [ChatClient API](https://docs.spring.io/spring-ai/reference/api/chatclient.html):
String response = ChatClient.create(chatModel).prompt()
.user(u -> u.text("Explain what do you see on this picture?")
.media(MimeTypeUtils.IMAGE\_PNG, new ClassPathResource("/multimodal.test.png")))
.call()
.content();
and produce a response like:
> This is an image of a fruit bowl with a simple design. The bowl is made of metal with curved wire edges that create an open structure, allowing the fruit to be visible from all angles. Inside the bowl, there are two yellow bananas resting on top of what appears to be a red apple. The bananas are slightly overripe, as indicated by the brown spots on their peels. The bowl has a metal ring at the top, likely to serve as a handle for carrying. The bowl is placed on a flat surface with a neutral-colored background that provides a clear view of the fruit inside.
Latest (1.0.0-SANPSHOT and 1.0.0-M1) versions of Spring AI provides multimodal support for the following Chat Clients:
* [Open AI - (GPT-4o)](https://docs.spring.io/spring-ai/reference/1.0-SNAPSHOT/api/chat/openai-chat.html#_multimodal)
* [Ollama - (LLaVa and Baklava models)](https://docs.spring.io/spring-ai/reference/1.0-SNAPSHOT/api/chat/ollama-chat.html#_multimodal)
* [Vertex AI Gemini - (gemini-pro-1.5 model)](https://docs.spring.io/spring-ai/reference/1.0-SNAPSHOT/api/chat/vertexai-gemini-chat.html#_multimodal)
* [Anthropic Claude 3](https://docs.spring.io/spring-ai/reference/1.0-SNAPSHOT/api/chat/anthropic-chat.html#_multimodal)
* [AWS Bedrock Anthropic Claude 3](https://docs.spring.io/spring-ai/reference/1.0-SNAPSHOT/api/chat/bedrock/bedrock-anthropic3.html#_multimodal)
## Next steps
Next, the Spring AI will rework the Document API to add multimodality support similar to the Message API.
Currently the AWS Bedrock [Titan EmbeddingClient](https://docs.spring.io/spring-ai/reference/1.0-SNAPSHOT/api/embeddings/bedrock-titan-embedding.html) supports image embeddings.
It would be required to integrate additional multimodal Embedding services to allow encoding, storing and searching of multimodal content in the Vector Stores.
# Conclusion
Traditionally, machine learning focused on specialised models for singular modalities. However, with innovations like OpenAI's GPT-4 Vision and Google's Vertex AI Gemini, a new era has dawned.
As we embrace this era of multimodal AI, the vision of interconnected learning envisioned by Comenius becomes a reality.
Spring AI's Message API facilitates the integration of multimodal LLMs, enabling developers to create innovative solutions. By leveraging these models, applications can comprehend and respond to data in various forms, unlocking new possibilities for AI-driven experiences.
# Prompt Engineering Techniques with Spring AI
Source: https://springaicommunity.mintlify.app/blog/prompts-and-output/prompt-engineering-patterns
This blog post demonstrates practical implementations of Prompt Engineering techniques using [Spring AI](https://docs.spring.io/spring-ai/reference/index.html).
This blog post demonstrates practical implementations of Prompt Engineering techniques using [Spring AI](https://docs.spring.io/spring-ai/reference/index.html).
The examples and patterns in this article are based on the comprehensive [Prompt Engineering Guide](https://www.kaggle.com/whitepaper-prompt-engineering) that covers the theory, principles, and patterns of effective prompt engineering.
The blog shows how to translate those concepts into working Java code using Spring AI's fluent [ChatClient API](https://docs.spring.io/spring-ai/reference/api/chatclient.html).
For convenience, the examples are structured to follow the same patterns and techniques outlined in the original guide.
The demo source code used in this article is available at: [https://github.com/spring-projects/spring-ai-examples/tree/main/prompt-engineering/prompt-engineering-patterns](https://github.com/spring-projects/spring-ai-examples/tree/main/prompt-engineering/prompt-engineering-patterns)
## 1. Configuration
The configuration section outlines how to set up and tune your Large Language Model (LLM) with Spring AI.
It covers selecting the right LLM provider for your use case and configuring important generation parameters that control the quality, style, and format of model outputs.
### LLM Provider Selection
For prompt engineering, you will start by choosing a model.
Spring AI supports [multiple LLM providers](https://docs.spring.io/spring-ai/reference/api/chat/comparison.html) (such as OpenAI, Anthropic, Google Vertex AI, AWS Bedrock, Ollama, and more), letting you switch providers without changing application code—just update your configuration.
Just add the selected starter dependency `spring-ai-starter-model-`.
For example, here is how to enable Anthropic Claude API:
```xml theme={null}
org.springframework.ai
spring-ai-starter-model-anthropic
```
along with some connection properties:
`spring.ai.anthropic.api-key=${ANTHROPIC_API_KEY}`
You can specify a particular LLM model name like this:
```java theme={null}
.options(ChatOptions.builder()
.model("claude-3-7-sonnet-latest") // Use Anthropic's Claude model
.build())
```
Find detailed information for enabling and configuring the preferred AI model in the [reference docs](https://docs.spring.io/spring-ai/reference/api/chatmodel.html).
### LLM Output Configuration
Before we dive into prompt engineering techniques, it's essential to understand how to configure the LLM's output behavior. Spring AI provides several configuration options that let you control various aspects of generation through the [ChatOptions](https://docs.spring.io/spring-ai/reference/api/chatmodel.html#_chat_options) builder.
All configurations can be applied programmatically as demonstrated in the examples below or through Spring application properties at start time.
#### Temperature
Temperature controls the randomness or "creativity" of the model's response.
* **Lower values (0.0-0.3)**: More deterministic, focused responses. Better for factual questions, classification, or tasks where consistency is critical.
* **Medium values (0.4-0.7)**: Balanced between determinism and creativity. Good for general use cases.
* **Higher values (0.8-1.0)**: More creative, varied, and potentially surprising responses. Better for creative writing, brainstorming, or generating diverse options.
```java theme={null}
.options(ChatOptions.builder()
.temperature(0.1) // Very deterministic output
.build())
```
Understanding temperature is crucial for prompt engineering as different techniques benefit from different temperature settings.
#### Output Length (MaxTokens)
The `maxTokens` parameter limits how many tokens (word pieces) the model can generate in its response.
* **Low values (5-25)**: For single words, short phrases, or classification labels.
* **Medium values (50-500)**: For paragraphs or short explanations.
* **High values (1000+)**: For long-form content, stories, or complex explanations.
```java theme={null}
.options(ChatOptions.builder()
.maxTokens(250) // Medium-length response
.build())
```
Setting appropriate output length is important to ensure you get complete responses without unnecessary verbosity.
#### Sampling Controls (Top-K and Top-P)
These parameters give you fine-grained control over the token selection process during generation.
* **Top-K**: Limits token selection to the K most likely next tokens. Higher values (e.g., 40-50) introduce more diversity.
* **Top-P (nucleus sampling)**: Dynamically selects from the smallest set of tokens whose cumulative probability exceeds P. Values like 0.8-0.95 are common.
```java theme={null}
.options(ChatOptions.builder()
.topK(40) // Consider only the top 40 tokens
.topP(0.8) // Sample from tokens that cover 80% of probability mass
.build())
```
These sampling controls work in conjunction with temperature to shape response characteristics.
#### Structured Response Format
Along with the plain text response (using `.content()`), Spring AI makes it easy to directly map LLM responses to Java objects using the `.entity()` method.
```java theme={null}
enum Sentiment {
POSITIVE, NEUTRAL, NEGATIVE
}
Sentiment result = chatClient.prompt("...")
.call()
.entity(Sentiment.class);
```
This feature is particularly powerful when combined with system prompts that instruct the model to return structured data.
#### Model-Specific Options
While the portable `ChatOptions` provides a consistent interface across different LLM providers, Spring AI also offers model-specific options classes that expose provider-specific features and configurations. These model-specific options allow you to leverage the unique capabilities of each LLM provider.
```java theme={null}
// Using OpenAI-specific options
OpenAiChatOptions openAiOptions = OpenAiChatOptions.builder()
.model("gpt-4o")
.temperature(0.2)
.frequencyPenalty(0.5) // OpenAI-specific parameter
.presencePenalty(0.3) // OpenAI-specific parameter
.responseFormat(new ResponseFormat("json_object")) // OpenAI-specific JSON mode
.seed(42) // OpenAI-specific deterministic generation
.build();
String result = chatClient.prompt("...")
.options(openAiOptions)
.call()
.content();
// Using Anthropic-specific options
AnthropicChatOptions anthropicOptions = AnthropicChatOptions.builder()
.model("claude-3-7-sonnet-latest")
.temperature(0.2)
.topK(40) // Anthropic-specific parameter
.thinking(AnthropicApi.ThinkingType.ENABLED, 1000) // Anthropic-specific thinking configuration
.build();
String result = chatClient.prompt("...")
.options(anthropicOptions)
.call()
.content();
```
Each model provider has its own implementation of chat options (e.g., `OpenAiChatOptions`, `AnthropicChatOptions`, `MistralAiChatOptions`) that exposes provider-specific parameters while still implementing the common interface. This approach gives you the flexibility to use portable options for cross-provider compatibility or model-specific options when you need access to unique features of a particular provider.
Note that when using model-specific options, your code becomes tied to that specific provider, reducing portability. It's a trade-off between accessing advanced provider-specific features versus maintaining provider independence in your application.
## 2. Prompt Engineering Techniques
Each section below implements a specific prompt engineering technique from the guide.
By following both the "Prompt Engineering" guide and these implementations, you'll develop a thorough understanding of not just what prompt engineering techniques are available, but how to effectively implement them in production Java applications.
### 2.1 Zero-Shot Prompting
Zero-shot prompting involves asking an AI to perform a task without providing any examples. This approach tests the model's ability to understand and execute instructions from scratch. Large language models are trained on vast corpora of text, allowing them to understand what tasks like "translation," "summarization," or "classification" entail without explicit demonstrations.
Zero-shot is ideal for straightforward tasks where the model likely has seen similar examples during training, and when you want to minimize prompt length. However, performance may vary depending on task complexity and how well the instructions are formulated.
```java theme={null}
// Implementation of Section 2.1: General prompting / zero shot (page 15)
public void pt_zero_shot(ChatClient chatClient) {
enum Sentiment {
POSITIVE, NEUTRAL, NEGATIVE
}
Sentiment reviewSentiment = chatClient.prompt("""
Classify movie reviews as POSITIVE, NEUTRAL or NEGATIVE.
Review: "Her" is a disturbing study revealing the direction
humanity is headed if AI is allowed to keep evolving,
unchecked. I wish there were more movies like this masterpiece.
Sentiment:
""")
.options(ChatOptions.builder()
.model("claude-3-7-sonnet-latest")
.temperature(0.1)
.maxTokens(5)
.build())
.call()
.entity(Sentiment.class);
System.out.println("Output: " + reviewSentiment);
}
```
This example shows how to classify a movie review sentiment without providing examples. Note the low temperature (0.1) for more deterministic results and the direct `.entity(Sentiment.class)` mapping to a Java enum.
**Reference:** Brown, T. B., et al. (2020). "Language Models are Few-Shot Learners." arXiv:2005.14165. [https://arxiv.org/abs/2005.14165](https://arxiv.org/abs/2005.14165)
### 2.2 One-Shot & Few-Shot Prompting
Few-shot prompting provides the model with one or more examples to help guide its responses, particularly useful for tasks requiring specific output formats. By showing the model examples of desired input-output pairs, it can learn the pattern and apply it to new inputs without explicit parameter updates.
One-shot provides a single example, which is useful when examples are costly or when the pattern is relatively simple. Few-shot uses multiple examples (typically 3-5) to help the model better understand patterns in more complex tasks or to illustrate different variations of correct outputs.
```java theme={null}
// Implementation of Section 2.2: One-shot & few-shot (page 16)
public void pt_ones_shot_few_shots(ChatClient chatClient) {
String pizzaOrder = chatClient.prompt("""
Parse a customer's pizza order into valid JSON
EXAMPLE 1:
I want a small pizza with cheese, tomato sauce, and pepperoni.
JSON Response:
```
\{
"size": "small",
"type": "normal",
"ingredients": \["cheese", "tomato sauce", "pepperoni"]
}
```
EXAMPLE 2:
Can I get a large pizza with tomato sauce, basil and mozzarella.
JSON Response:
```
\{
"size": "large",
"type": "normal",
"ingredients": \["tomato sauce", "basil", "mozzarella"]
}
```
Now, I would like a large pizza, with the first half cheese and mozzarella.
And the other tomato sauce, ham and pineapple.
""")
.options(ChatOptions.builder()
.model("claude-3-7-sonnet-latest")
.temperature(0.1)
.maxTokens(250)
.build())
.call()
.content();
}
```
Few-shot prompting is especially effective for tasks requiring specific formatting, handling edge cases, or when the task definition might be ambiguous without examples. The quality and diversity of the examples significantly impact performance.
**Reference:** Brown, T. B., et al. (2020). "Language Models are Few-Shot Learners." arXiv:2005.14165. [https://arxiv.org/abs/2005.14165](https://arxiv.org/abs/2005.14165)
### 2.3 System, contextual and role prompting
#### System Prompting
System prompting sets the overall context and purpose for the language model, defining the "big picture" of what the model should be doing. It establishes the behavioral framework, constraints, and high-level objectives for the model's responses, separate from the specific user queries.
System prompts act as a persistent "mission statement" throughout the conversation, allowing you to set global parameters like output format, tone, ethical boundaries, or role definitions. Unlike user prompts which focus on specific tasks, system prompts frame how all user prompts should be interpreted.
```java theme={null}
// Implementation of Section 2.3.1: System prompting
public void pt_system_prompting_1(ChatClient chatClient) {
String movieReview = chatClient
.prompt()
.system("Classify movie reviews as positive, neutral or negative. Only return the label in uppercase.")
.user("""
Review: "Her" is a disturbing study revealing the direction
humanity is headed if AI is allowed to keep evolving,
unchecked. It's so disturbing I couldn't watch it.
Sentiment:
""")
.options(ChatOptions.builder()
.model("claude-3-7-sonnet-latest")
.temperature(1.0)
.topK(40)
.topP(0.8)
.maxTokens(5)
.build())
.call()
.content();
}
```
System prompting is particularly powerful when combined with Spring AI's entity mapping capabilities:
```java theme={null}
// Implementation of Section 2.3.1: System prompting with JSON output
record MovieReviews(Movie[] movie_reviews) {
enum Sentiment {
POSITIVE, NEUTRAL, NEGATIVE
}
record Movie(Sentiment sentiment, String name) {
}
}
MovieReviews movieReviews = chatClient
.prompt()
.system("""
Classify movie reviews as positive, neutral or negative. Return
valid JSON.
""")
.user("""
Review: "Her" is a disturbing study revealing the direction
humanity is headed if AI is allowed to keep evolving,
unchecked. It's so disturbing I couldn't watch it.
JSON Response:
""")
.call()
.entity(MovieReviews.class);
```
System prompts are especially valuable for multi-turn conversations, ensuring consistent behavior across multiple queries, and for establishing format constraints like JSON output that should apply to all responses.
**Reference:** OpenAI. (2022). "System Message." [https://platform.openai.com/docs/guides/chat/introduction](https://platform.openai.com/docs/guides/chat/introduction)
#### Role Prompting
Role prompting instructs the model to adopt a specific role or persona, which affects how it generates content. By assigning a particular identity, expertise, or perspective to the model, you can influence the style, tone, depth, and framing of its responses.
Role prompting leverages the model's ability to simulate different expertise domains and communication styles. Common roles include expert (e.g., "You are an experienced data scientist"), professional (e.g., "Act as a travel guide"), or stylistic character (e.g., "Explain like you're Shakespeare").
```java theme={null}
// Implementation of Section 2.3.2: Role prompting
public void pt_role_prompting_1(ChatClient chatClient) {
String travelSuggestions = chatClient
.prompt()
.system("""
I want you to act as a travel guide. I will write to you
about my location and you will suggest 3 places to visit near
me. In some cases, I will also give you the type of places I
will visit.
""")
.user("""
My suggestion: "I am in Amsterdam and I want to visit only museums."
Travel Suggestions:
""")
.call()
.content();
}
```
Role prompting can be enhanced with style instructions:
```java theme={null}
// Implementation of Section 2.3.2: Role prompting with style instructions
public void pt_role_prompting_2(ChatClient chatClient) {
String humorousTravelSuggestions = chatClient
.prompt()
.system("""
I want you to act as a travel guide. I will write to you about
my location and you will suggest 3 places to visit near me in
a humorous style.
""")
.user("""
My suggestion: "I am in Amsterdam and I want to visit only museums."
Travel Suggestions:
""")
.call()
.content();
}
```
This technique is particularly effective for specialized domain knowledge, achieving a consistent tone across responses, and creating more engaging, personalized interactions with users.
**Reference:** Shanahan, M., et al. (2023). "Role-Play with Large Language Models." arXiv:2305.16367. [https://arxiv.org/abs/2305.16367](https://arxiv.org/abs/2305.16367)
#### Contextual Prompting
Contextual prompting provides additional background information to the model by passing context parameters. This technique enriches the model's understanding of the specific situation, enabling more relevant and tailored responses without cluttering the main instruction.
By supplying contextual information, you help the model understand the specific domain, audience, constraints, or background facts relevant to the current query. This leads to more accurate, relevant, and appropriately framed responses.
```java theme={null}
// Implementation of Section 2.3.3: Contextual prompting
public void pt_contextual_prompting(ChatClient chatClient) {
String articleSuggestions = chatClient
.prompt()
.user(u -> u.text("""
Suggest 3 topics to write an article about with a few lines of
description of what this article should contain.
Context: {context}
""")
.param("context", "You are writing for a blog about retro 80's arcade video games."))
.call()
.content();
}
```
Spring AI makes contextual prompting clean with the param() method to inject context variables. This technique is particularly valuable when the model needs specific domain knowledge, when adapting responses to particular audiences or scenarios, and for ensuring responses are aligned with particular constraints or requirements.
**Reference:** Liu, P., et al. (2021). "What Makes Good In-Context Examples for GPT-3?" arXiv:2101.06804. [https://arxiv.org/abs/2101.06804](https://arxiv.org/abs/2101.06804)
### 2.4 Step-Back Prompting
Step-back prompting breaks complex requests into simpler steps by first acquiring background knowledge. This technique encourages the model to first "step back" from the immediate question to consider the broader context, fundamental principles, or general knowledge relevant to the problem before addressing the specific query.
By decomposing complex problems into more manageable components and establishing foundational knowledge first, the model can provide more accurate responses to difficult questions.
```java theme={null}
// Implementation of Section 2.4: Step-back prompting
public void pt_step_back_prompting(ChatClient.Builder chatClientBuilder) {
// Set common options for the chat client
var chatClient = chatClientBuilder
.defaultOptions(ChatOptions.builder()
.model("claude-3-7-sonnet-latest")
.temperature(1.0)
.topK(40)
.topP(0.8)
.maxTokens(1024)
.build())
.build();
// First get high-level concepts
String stepBack = chatClient
.prompt("""
Based on popular first-person shooter action games, what are
5 fictional key settings that contribute to a challenging and
engaging level storyline in a first-person shooter video game?
""")
.call()
.content();
// Then use those concepts in the main task
String story = chatClient
.prompt()
.user(u -> u.text("""
Write a one paragraph storyline for a new level of a first-
person shooter video game that is challenging and engaging.
Context: {step-back}
""")
.param("step-back", stepBack))
.call()
.content();
}
```
Step-back prompting is particularly effective for complex reasoning tasks, problems requiring specialized domain knowledge, and when you want more comprehensive and thoughtful responses rather than immediate answers.
**Reference:** Zheng, Z., et al. (2023). "Take a Step Back: Evoking Reasoning via Abstraction in Large Language Models." arXiv:2310.06117. [https://arxiv.org/abs/2310.06117](https://arxiv.org/abs/2310.06117)
### 2.5 Chain of Thought (CoT)
Chain of Thought prompting encourages the model to reason step-by-step through a problem, which improves accuracy for complex reasoning tasks. By explicitly asking the model to show its work or think through a problem in logical steps, you can dramatically improve performance on tasks requiring multi-step reasoning.
CoT works by encouraging the model to generate intermediate reasoning steps before producing a final answer, similar to how humans solve complex problems. This makes the model's thinking process explicit and helps it arrive at more accurate conclusions.
```java theme={null}
// Implementation of Section 2.5: Chain of Thought (CoT) - Zero-shot approach
public void pt_chain_of_thought_zero_shot(ChatClient chatClient) {
String output = chatClient
.prompt("""
When I was 3 years old, my partner was 3 times my age. Now,
I am 20 years old. How old is my partner?
Let's think step by step.
""")
.call()
.content();
}
// Implementation of Section 2.5: Chain of Thought (CoT) - Few-shot approach
public void pt_chain_of_thought_singleshot_fewshots(ChatClient chatClient) {
String output = chatClient
.prompt("""
Q: When my brother was 2 years old, I was double his age. Now
I am 40 years old. How old is my brother? Let's think step
by step.
A: When my brother was 2 years, I was 2 * 2 = 4 years old.
That's an age difference of 2 years and I am older. Now I am 40
years old, so my brother is 40 - 2 = 38 years old. The answer
is 38.
Q: When I was 3 years old, my partner was 3 times my age. Now,
I am 20 years old. How old is my partner? Let's think step
by step.
A:
""")
.call()
.content();
}
```
The key phrase "Let's think step by step" triggers the model to show its reasoning process. CoT is especially valuable for mathematical problems, logical reasoning tasks, and any question requiring multi-step reasoning. It helps reduce errors by making intermediate reasoning explicit.
**Reference:** Wei, J., et al. (2022). "Chain-of-Thought Prompting Elicits Reasoning in Large Language Models." arXiv:2201.11903. [https://arxiv.org/abs/2201.11903](https://arxiv.org/abs/2201.11903)
### 2.6 Self-Consistency
Self-consistency involves running the model multiple times and aggregating results for more reliable answers. This technique addresses the variability in LLM outputs by sampling diverse reasoning paths for the same problem and selecting the most consistent answer through majority voting.
By generating multiple reasoning paths with different temperature or sampling settings, then aggregating the final answers, self-consistency improves accuracy on complex reasoning tasks. It's essentially an ensemble method for LLM outputs.
```java theme={null}
// Implementation of Section 2.6: Self-consistency
public void pt_self_consistency(ChatClient chatClient) {
String email = """
Hi,
I have seen you use Wordpress for your website. A great open
source content management system. I have used it in the past
too. It comes with lots of great user plugins. And it's pretty
easy to set up.
I did notice a bug in the contact form, which happens when
you select the name field. See the attached screenshot of me
entering text in the name field. Notice the JavaScript alert
box that I inv0k3d.
But for the rest it's a great website. I enjoy reading it. Feel
free to leave the bug in the website, because it gives me more
interesting things to read.
Cheers,
Harry the Hacker.
""";
record EmailClassification(Classification classification, String reasoning) {
enum Classification {
IMPORTANT, NOT_IMPORTANT
}
}
int importantCount = 0;
int notImportantCount = 0;
// Run the model 5 times with the same input
for (int i = 0; i < 5; i++) {
EmailClassification output = chatClient
.prompt()
.user(u -> u.text("""
Email: {email}
Classify the above email as IMPORTANT or NOT IMPORTANT. Let's
think step by step and explain why.
""")
.param("email", email))
.options(ChatOptions.builder()
.temperature(1.0) // Higher temperature for more variation
.build())
.call()
.entity(EmailClassification.class);
// Count results
if (output.classification() == EmailClassification.Classification.IMPORTANT) {
importantCount++;
} else {
notImportantCount++;
}
}
// Determine the final classification by majority vote
String finalClassification = importantCount > notImportantCount ?
"IMPORTANT" : "NOT IMPORTANT";
}
```
Self-consistency is particularly valuable for high-stakes decisions, complex reasoning tasks, and when you need more confident answers than a single response can provide. The trade-off is increased computational cost and latency due to multiple API calls.
**Reference:** Wang, X., et al. (2022). "Self-Consistency Improves Chain of Thought Reasoning in Language Models." arXiv:2203.11171. [https://arxiv.org/abs/2203.11171](https://arxiv.org/abs/2203.11171)
### 2.7 Tree of Thoughts (ToT)
Tree of Thoughts (ToT) is an advanced reasoning framework that extends Chain of Thought by exploring multiple reasoning paths simultaneously. It treats problem-solving as a search process where the model generates different intermediate steps, evaluates their promise, and explores the most promising paths.
This technique is particularly powerful for complex problems with multiple possible approaches or when the solution requires exploring various alternatives before finding the optimal path.
> NOTE: The original "Prompt Engineering" guide doesn't provide implementation examples for ToT, likely due to its complexity. Below is a simplified example that demonstrates the core concept.
Game Solving ToT Example:
```java theme={null}
// Implementation of Section 2.7: Tree of Thoughts (ToT) - Game solving example
public void pt_tree_of_thoughts_game(ChatClient chatClient) {
// Step 1: Generate multiple initial moves
String initialMoves = chatClient
.prompt("""
You are playing a game of chess. The board is in the starting position.
Generate 3 different possible opening moves. For each move:
1. Describe the move in algebraic notation
2. Explain the strategic thinking behind this move
3. Rate the move's strength from 1-10
""")
.options(ChatOptions.builder()
.temperature(0.7)
.build())
.call()
.content();
// Step 2: Evaluate and select the most promising move
String bestMove = chatClient
.prompt()
.user(u -> u.text("""
Analyze these opening moves and select the strongest one:
{moves}
Explain your reasoning step by step, considering:
1. Position control
2. Development potential
3. Long-term strategic advantage
Then select the single best move.
""").param("moves", initialMoves))
.call()
.content();
// Step 3: Explore future game states from the best move
String gameProjection = chatClient
.prompt()
.user(u -> u.text("""
Based on this selected opening move:
{best_move}
Project the next 3 moves for both players. For each potential branch:
1. Describe the move and counter-move
2. Evaluate the resulting position
3. Identify the most promising continuation
Finally, determine the most advantageous sequence of moves.
""").param("best_move", bestMove))
.call()
.content();
}
```
**Reference:** Yao, S., et al. (2023). "Tree of Thoughts: Deliberate Problem Solving with Large Language Models." arXiv:2305.10601. [https://arxiv.org/abs/2305.10601](https://arxiv.org/abs/2305.10601)
### 2.8 Automatic Prompt Engineering
Automatic Prompt Engineering uses the AI to generate and evaluate alternative prompts. This meta-technique leverages the language model itself to create, refine, and benchmark different prompt variations to find optimal formulations for specific tasks.
By systematically generating and evaluating prompt variations, APE can find more effective prompts than manual engineering, especially for complex tasks. It's a way of using AI to improve its own performance.
```java theme={null}
// Implementation of Section 2.8: Automatic Prompt Engineering
public void pt_automatic_prompt_engineering(ChatClient chatClient) {
// Generate variants of the same request
String orderVariants = chatClient
.prompt("""
We have a band merchandise t-shirt webshop, and to train a
chatbot we need various ways to order: "One Metallica t-shirt
size S". Generate 10 variants, with the same semantics but keep
the same meaning.
""")
.options(ChatOptions.builder()
.temperature(1.0) // High temperature for creativity
.build())
.call()
.content();
// Evaluate and select the best variant
String output = chatClient
.prompt()
.user(u -> u.text("""
Please perform BLEU (Bilingual Evaluation Understudy) evaluation on the following variants:
----
{variants}
----
Select the instruction candidate with the highest evaluation score.
""").param("variants", orderVariants))
.call()
.content();
}
```
APE is particularly valuable for optimizing prompts for production systems, addressing challenging tasks where manual prompt engineering has reached its limits, and for systematically improving prompt quality at scale.
**Reference:** Zhou, Y., et al. (2022). "Large Language Models Are Human-Level Prompt Engineers." arXiv:2211.01910. [https://arxiv.org/abs/2211.01910](https://arxiv.org/abs/2211.01910)
### 2.9 Code Prompting
Code prompting refers to specialized techniques for code-related tasks. These techniques leverage LLMs' ability to understand and generate programming languages, enabling them to write new code, explain existing code, debug issues, and translate between languages.
Effective code prompting typically involves clear specifications, appropriate context (libraries, frameworks, style guidelines), and sometimes examples of similar code. Temperature settings tend to be lower (0.1-0.3) for more deterministic outputs.
```java theme={null}
// Implementation of Section 2.9.1: Prompts for writing code
public void pt_code_prompting_writing_code(ChatClient chatClient) {
String bashScript = chatClient
.prompt("""
Write a code snippet in Bash, which asks for a folder name.
Then it takes the contents of the folder and renames all the
files inside by prepending the name draft to the file name.
""")
.options(ChatOptions.builder()
.temperature(0.1) // Low temperature for deterministic code
.build())
.call()
.content();
}
// Implementation of Section 2.9.2: Prompts for explaining code
public void pt_code_prompting_explaining_code(ChatClient chatClient) {
String code = """
#!/bin/bash
echo "Enter the folder name: "
read folder_name
if [ ! -d "$folder_name" ]; then
echo "Folder does not exist."
exit 1
fi
files=( "$folder_name"/* )
for file in "${files[@]}"; do
new_file_name="draft_$(basename "$file")"
mv "$file" "$new_file_name"
done
echo "Files renamed successfully."
""";
String explanation = chatClient
.prompt()
.user(u -> u.text("""
Explain to me the below Bash code:
```
\{code}
```
""").param("code", code))
.call()
.content();
}
// Implementation of Section 2.9.3: Prompts for translating code
public void pt_code_prompting_translating_code(ChatClient chatClient) {
String bashCode = """
#!/bin/bash
echo "Enter the folder name: "
read folder_name
if [ ! -d "$folder_name" ]; then
echo "Folder does not exist."
exit 1
fi
files=( "$folder_name"/* )
for file in "${files[@]}"; do
new_file_name="draft_$(basename "$file")"
mv "$file" "$new_file_name"
done
echo "Files renamed successfully."
""";
String pythonCode = chatClient
.prompt()
.user(u -> u.text("""
Translate the below Bash code to a Python snippet:
{code}
""").param("code", bashCode))
.call()
.content();
}
```
Code prompting is especially valuable for automated code documentation, prototyping, learning programming concepts, and translating between programming languages. The effectiveness can be further enhanced by combining it with techniques like few-shot prompting or chain-of-thought.
**Reference:** Chen, M., et al. (2021). "Evaluating Large Language Models Trained on Code." arXiv:2107.03374. [https://arxiv.org/abs/2107.03374](https://arxiv.org/abs/2107.03374)
## Conclusion
Spring AI provides an elegant Java API for implementing all major prompt engineering techniques. By combining these techniques with Spring's powerful entity mapping and fluent API, developers can build sophisticated AI-powered applications with clean, maintainable code.
The most effective approach often involves combining multiple techniques—for example, using system prompts with few-shot examples, or chain-of-thought with role prompting. Spring AI's flexible API makes these combinations straightforward to implement.
For production applications, remember to:
1. Test prompts with different parameters (temperature, top-k, top-p)
2. Consider using self-consistency for critical decision-making
3. Leverage Spring AI's entity mapping for type-safe responses
4. Use contextual prompting to provide application-specific knowledge
With these techniques and Spring AI's powerful abstractions, you can create robust AI-powered applications that deliver consistent, high-quality results.
## References
1. Brown, T. B., et al. (2020). "Language Models are Few-Shot Learners." arXiv:2005.14165.
2. Wei, J., et al. (2022). "Chain-of-Thought Prompting Elicits Reasoning in Large Language Models." arXiv:2201.11903.
3. Wang, X., et al. (2022). "Self-Consistency Improves Chain of Thought Reasoning in Language Models." arXiv:2203.11171.
4. Yao, S., et al. (2023). "Tree of Thoughts: Deliberate Problem Solving with Large Language Models." arXiv:2305.10601.
5. Zhou, Y., et al. (2022). "Large Language Models Are Human-Level Prompt Engineers." arXiv:2211.01910.
6. Zheng, Z., et al. (2023). "Take a Step Back: Evoking Reasoning via Abstraction in Large Language Models." arXiv:2310.06117.
7. Liu, P., et al. (2021). "What Makes Good In-Context Examples for GPT-3?" arXiv:2101.06804.
8. Shanahan, M., et al. (2023). "Role-Play with Large Language Models." arXiv:2305.16367.
9. Chen, M., et al. (2021). "Evaluating Large Language Models Trained on Code." arXiv:2107.03374.
10. [Spring AI Documentation](https://docs.spring.io/spring-ai/reference/index.html)
11. [ChatClient API Reference](https://docs.spring.io/spring-ai/reference/api/chatclient.html)
12. [Google's Prompt Engineering Guide](https://www.kaggle.com/whitepaper-prompt-engineering)
# Repository Vector Search Methods
Source: https://springaicommunity.mintlify.app/blog/prompts-and-output/vector-search-methods
The emergence of Large Language Models (LLM) has propelled Generative AI and surfaced one of its key components to a broad audience:...
The emergence of Large Language Models (LLM) has propelled Generative AI and surfaced one of its key components to a broad audience: [Embeddings](https://en.wikipedia.org/wiki/Word_embedding).
Embeddings are a [vector representation](https://en.wikipedia.org/wiki/Vector_space) of data in a high-dimensional space capturing their semantic meaning. Vector representations allow for more efficient and effective search (Vector Search) of similar items. Vector search is typically used to build [Retrieval-augmented generation (RAG)](https://en.wikipedia.org/wiki/Retrieval-augmented_generation) systems and so there is demand for vector databases.
While new vector databases are on the rise, existing database engines are gradually incorporating vector search capabilities leading to two main types of databases:
* Dedicated Vector Databases
* Database Engines with Vector Search Capabilities
Dedicated Vector Databases originate in the need for searching for similar items in high-dimensional spaces. They are optimized for this purpose and often use specialized indexing techniques to improve search performance. Examples include [Pinecone](https://www.pinecone.io/), [Weaviate](https://weaviate.io/), [Milvus](https://milvus.io/), and [Qdrant](https://qdrant.tech/). All of these are projects emerged around the early 2020s.
A Vector Search typically requires a vector, which is an array of single-precision `float` numbers, a namespace (something like a table or collection) and a Top K (the number of results to return) argument. Vector search then run an Approximate Nearest Neighbor (ANN) or k-Nearest Neighbors (kNN) search.
Those databases allow additional filtering similarity and metadata fields, however, the core of the search gravitates around vector representations.
Existing Database Engines such as Postgres (pgvector), Oracle, and MongoDB have gradually added vector search capabilities to their engines.
They are not dedicated vector databases but rather general-purpose databases with vector search capabilities. Their strength lies in their ability to handle a wide range of data types and queries, especially when it comes to combining vector search with traditional queries. They also have a long history of supporting administrative tasks with a well-understood operating model for backup and recovery, scaling, and maintenance.
Another aspect to consider is that these databases are already being used in production, containing large amounts of existing data.
## Elephant in the Room
[Spring AI](https://docs.spring.io/spring-ai/reference/api/vectordbs.html) has a wide range of integrations with vector stores.
The obvious question is: "Why has Spring AI support for Vector Search and Spring Data does not?". And why does that even matter?
The goal of Spring AI is to simplify the process of building AI-powered applications by providing a consistent programming model and abstractions. It focuses on integrating AI capabilities into Spring applications and provides a unified API for working with various AI models and services.
AI is a hot topic: Several database vendors have contributed their integration for vector search to Spring AI to enable use-cases such as [Retrieval Augmented Generation](https://docs.spring.io/spring-ai/reference/concepts.html#concept-rag). This is a great example of how Open Source can drive innovation and collaboration in the database space.
When we consider what's after the peak of AI's hype cycle, we are faced with day-2 operations. Data has a lifecycle, new LLM models come and go, some are better for certain tasks or languages than others. While Spring AI's `VectorStore` has the means to reflect of data lifecycle to some extent, it is by no means its primary focus.
And here comes Spring Data into play. Spring Data is all about data models, access, and data lifecycle. It provides a consistent programming model for accessing different data stores, including relational databases, NoSQL databases, and more. Spring Data's focus is on simplifying data access and management, making it easier to work with various data sources in a consistent way.
Wouldn't it then make sense to have Vector Search capabilities in Spring Data?
Yes, it would.
## Vector Search in Spring Data
With Spring Data 3.5, we've introduced a `Vector` type to simplify usage of vector data in entities.
Vector data types are not common in typical domain models.
The closest resemblance of vector data has been geospatial data types such as `Point`, `Polygon`, etc. but even those are not common.
Domain models rather consist of primitive types and value types reflecting the domain they are used in.
Vector properties use either vendor-specific types (such as Cassandra's `CqlVector`) or use some sort of array, like `float[]`. In the latter case, using arrays introduces quite some accidental complexity: Java arrays are pointers. Their underlying actual array data is mutable. Carrying arrays around isn't too common either.
Your domain model can leverage `Vector` property type reducing the risk of accidentally mutating the underlying data and giving the otherwise `float[]` a semantic context. Persisting and retrieving `Vector` properties is handled by Spring Data for modules where Spring Data handles object mapping. For JPA, you will require additional converters.
```java theme={null}
Vector vector = Vector.of(0.0001f, 1.12345f, 2.23456f, 3.34567f, 4.45678f);
```
`Vector.of(…)` creates an immutable `Vector` instance and a copy of the given input array. While this is useful for most scenarios, performance-sensitive arrangements that want to reduce GC pressure can retain the reference to the original array:
```java theme={null}
Vector vector = Vector.unsafe(new float[]{0.0001f, 1.12345f, 2.23456f, 3.34567f, 4.45678f});
```
You can obtain a safe (copied) variant of the `float[]` array by calling `toFloatArray()` respective `toDoubleArray()` if you want to use `double[]`. Alternatively, you can access the `Vector`'s source through `getSource()`.
Depending on your data store, you might need to equip your data model with additional annotations to indicate e.g. the number of dimensions or its precision.
When running a Vector search operation, each database uses a very different API. Let's take a look at MongoDB and Apache Cassandra.
In MongoDB, Vector Search is used through its Aggregation Framework requiring an aggregation stage:
```java theme={null}
class Comment {
String id;
String language;
String comment;
Vector embedding;
// getters, setters, …
}
VectorSearchOperation vectorSearch = VectorSearchOperation.search("euclidean-index")
.path("embedding")
.vector(0.0001f, 1.12345f, 2.23456f, 3.34567f, 4.45678f) // float[], Vector, or Mongo's BinaryVector
.limit(10) // Top-K
.numCandidates(200)
.filter(where("language").is("DE"))
.searchType(SearchType.ANN)
.withSearchScore();
AggregationResults results = template.aggregate(newAggregation(vectorSearch),
Comment.class, Document.class);
```
`VectorSearchOperation` offers a fluent API guiding you through each step of the operation reflecting the underlying MongoDB API in a convenient way.
Let's have a look at Apache Cassandra. Cassandra uses CQL (Cassandra Query Language) to run queries against the database. Cassandra Vector Search uses the same approach. With Cassandra, Spring Data users have the choice to either use Spring Data Cassandra's `Query` API or the native CQL API to run a Vector Search:
```java theme={null}
@Table("comments")
class CommentVectorSearchResult {
@Id String id;
double score;
String language;
String comment;
// getters, setters, …
}
CassandraTemplate template = …
Query query = Query.select(Columns.from("id", "language", "comment")
.select("embedding", it -> it.similarity(Vector.of(1.1f, 2.2f)).cosine().as("score")))
.sort(VectorSort.ann("embedding", Vector.of(1.1f, 2.2f)))
.limit(10);
List select = template.select(query, VectorSearchResult.class);
```
The CQL variant would look like this:
```java theme={null}
CassandraTemplate template = …
List