AI · Spring Boot
Spring AI tool calling with Claude: a practical guide
Add tool calling to a Spring Boot app with Spring AI and Claude: define @Tool methods, register them with ChatClient, and design tools that are safe in production.
Tool calling lets a model like Claude ask your application to run a function — look up an order, check stock, query an API — and then use the result in its answer. Spring AI handles the back-and-forth for you. This guide shows the pattern in a Spring Boot service, and the design rules that keep it safe in production.
How tool calling works
- Your app sends the user’s message to the model, along with descriptions of the tools it may use.
- The model replies with a request to call a tool, with arguments it chose.
- Spring AI runs your Java method and sends the result back to the model.
- The model either calls another tool or writes the final answer.
The model never runs code itself: your application decides what each tool does, with your application’s permissions. That is why tool design matters so much.
Setup
Add the Spring AI Anthropic starter, with its version managed by the Spring AI BOM, and give it an API key through an environment variable — never commit keys.
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-anthropic</artifactId>
</dependency>spring:
ai:
anthropic:
api-key: ${ANTHROPIC_API_KEY}
chat:
options:
model: claude-sonnet-5-5Spring AI’s API changed on the way to 1.0 — older tutorials talk about “function calling” — so match examples to the version in your project.
Defining tools
A tool is a plain method annotated with @Tool. The descriptions are what the model reads to decide when and how to call it, so write them for the model, not for yourself:
@Component
public class OrderTools {
private final OrderRepository orders;
OrderTools(OrderRepository orders) {
this.orders = orders;
}
@Tool(description = "Look up the current status of a customer order by its order ID")
public String orderStatus(@ToolParam(description = "Order ID, for example ORD-1042") String orderId) {
return orders.findById(orderId)
.map(o -> o.status().name())
.orElse("NOT_FOUND");
}
}Registering tools with ChatClient
@RestController
class SupportController {
private final ChatClient chat;
SupportController(ChatClient.Builder builder, OrderTools tools) {
this.chat = builder
.defaultSystem("You are a support assistant. Use the tools to look up order data. Never guess an order status.")
.defaultTools(tools)
.build();
}
@GetMapping("/ask")
String ask(@RequestParam String question) {
return chat.prompt().user(question).call().content();
}
}Ask “Where is order ORD-1042?” and Claude will call orderStatus, receive the status, and answer in plain language.
Designing good tools
- One clear job per tool, with a precise description and narrow, typed parameters.
- Return compact, structured results — just the fields the model needs, not whole entities.
- Return useful errors such as
NOT_FOUNDinstead of throwing, so the model can recover gracefully. - Start read-only. Add tools that change data only once the read-only version works well.
Safety in production
- Enforce authorization inside the tool. Check that the current user may see that order — the model’s arguments are untrusted input.
- Require confirmation for side effects such as refunds or cancellations: have the tool prepare the action and let a person approve it.
- Validate arguments and apply timeouts to every downstream call a tool makes.
- Log every tool call with its arguments and result, so you can audit and debug conversations.
- Watch cost and latency: each tool round trip is another model call, so keep tool chains short.
Going further: MCP
When several applications need the same tools, the Model Context Protocol (MCP) lets you expose them once as an MCP server that any MCP-capable client can use. Spring AI includes support for building MCP servers and clients — a natural next step once your tools are proven inside one service.