Function Calling (Tool Use)
Give your AI superpowers by letting it invoke your Java methods. Function calling bridges the gap between language understanding and real-world actions—from querying databases to sending emails.
Function calling (also called "tool use") is the capability for LLMs to request execution of specific functions in your application when they need data or actions they can't perform themselves. Instead of hallucinating a stock price, the model calls getStockPrice("AAPL"). Instead of guessing, it uses your searchDatabase() function to find real answers.
Spring AI makes function calling type-safe and intuitive. You define functions as Java beans with annotated request/response records, register them with the ChatClient, and the model decides when to call them based on user intent.
How Function Calling Works
User sends a message
"What's the weather in Tokyo and book me a flight there"
Model analyzes available functions
Reviews function descriptions and parameters to determine which ones are needed
Model returns function call request (not final answer)
{ function: 'getWeather', args: { city: 'Tokyo' } }
Spring AI executes your Java function
Your function runs with the extracted parameters, returns structured data
Results sent back to model
Model receives function output and incorporates it into final response
Model generates final answer
"It's 22°C and sunny in Tokyo! I've found flights starting at $450..."
When to Use Function Calling
✓ Use Function Calling For
- • Real-time data — Stock prices, weather, live APIs
- • Actions — Send emails, create tickets, book appointments
- • Calculations — Math, date calculations, conversions
- • Database queries — When user asks for specific records
- • System integrations — CRM, ERP, payment systems
📚 Use RAG Instead For
- • Static knowledge — Documentation, policies, manuals
- • Context-heavy Q&A — Multi-paragraph explanations
- • Historical data — Past reports, archived content
- • Semantic search — Finding related content
- • Large text corpora — Thousands of documents
Defining Functions
Function as a Spring Bean
Implement Java's Function interface with annotated request/response records
@Component@JsonClassDescription("Get current stock price for a given ticker symbol")publicclassStockPriceFunctionimplementsFunction<StockRequest,StockResponse>{privatefinalStockApiClient stockApi;publicStockPriceFunction(StockApiClient stockApi){this.stockApi = stockApi;}@OverridepublicStockResponseapply(StockRequest request){try{StockData data = stockApi.getQuote(request.ticker());returnnewStockResponse(
request.ticker(),
data.price(),
data.change(),
data.percentChange(),true,null);}catch(Exception e){returnnewStockResponse(request.ticker(),0,0,0,false, e.getMessage());}}}// Request record - defines what the model can passrecordStockRequest(@JsonProperty(required =true, description ="Stock ticker symbol, e.g., AAPL, GOOGL, MSFT")String ticker
){}// Response record - what your function returns to the modelrecordStockResponse(String ticker,double price,double change,double percentChange,boolean success,String error
){}Registering Multiple Functions
ChatClient Configuration
Register all your functions when building the client
@ServicepublicclassAIAssistantService{privatefinalChatClient chatClient;publicAIAssistantService(ChatClient.Builder builder,StockPriceFunction stockFunction,WeatherFunction weatherFunction,SendEmailFunction emailFunction,SearchDatabaseFunction dbSearchFunction){this.chatClient = builder
.defaultSystem("""
You are a helpful assistant with access to tools.
Use functions when you need real-time data or to perform actions.
Always confirm before performing irreversible actions.
""").defaultFunctions(
stockFunction,
weatherFunction,
emailFunction,
dbSearchFunction
).build();}publicStringchat(String userMessage){return chatClient.prompt().user(userMessage).call().content();}// Or add functions per-requestpublicStringchatWithSpecificTools(String message,Function<?,?>... functions){return chatClient.prompt().user(message).functions(functions).call().content();}}Advanced Patterns
Confirmation Before Dangerous Actions
For actions like deleting data or sending emails, return a confirmation request instead of executing immediately.
@Component@JsonClassDescription("Delete a user account (requires confirmation)")publicclassDeleteUserFunctionimplementsFunction<DeleteRequest,DeleteResponse>{@OverridepublicDeleteResponseapply(DeleteRequest request){if(!request.confirmed()){// Return confirmation prompt instead of deletingreturnnewDeleteResponse(false,"Are you sure you want to delete user "+ request.userId()+"? "+"This action is irreversible. Say 'yes, delete user "+ request.userId()+"' to confirm.");}// Actually delete
userService.delete(request.userId());returnnewDeleteResponse(true,"User deleted successfully");}}recordDeleteRequest(@JsonProperty(required =true)String userId,@JsonProperty(description ="Set to true only if user explicitly confirmed")boolean confirmed
){}Chained Function Calls
Models can call multiple functions in sequence to accomplish complex tasks.
// User: "Find the cheapest flight to wherever is warmest right now"// Model calls:// 1. getWeatherForCities(["Miami", "LA", "Phoenix"]) → Phoenix is 38°C// 2. searchFlights(destination: "Phoenix") → Returns flight options // 3. Model synthesizes: "Phoenix is the warmest at 38°C. The cheapest flight is $299..."Function with Context/Memory
Pass conversation context to functions for stateful operations.
@Component@JsonClassDescription("Add item to user's shopping cart")publicclassAddToCartFunctionimplementsFunction<CartRequest,CartResponse>{privatefinalCartService cartService;privatefinalRequestContext requestContext;// Injected per-request@OverridepublicCartResponseapply(CartRequest request){// Get current user from request contextString userId = requestContext.getCurrentUserId();Cart cart = cartService.addItem(userId, request.productId(), request.quantity());returnnewCartResponse(
cart.getItemCount(),
cart.getTotal(),"Added "+ request.quantity()+" items to your cart");}}Security Considerations
⚠️ Validate All Inputs
The model extracts parameters from user text. A malicious user could craft prompts to inject unexpected values. Always validate.
⚠️ Authorization Checks
Functions should verify the user has permission. Don't trust that the model will enforce access control—it won't.
⚠️ Rate Limiting
A single user prompt could trigger many function calls. Implement rate limiting on expensive operations to prevent abuse.
⚠️ Audit Logging
Log all function calls with parameters, user ID, and timestamp. Essential for debugging and security audits.
Provider Support
| Provider | Function Calling | Parallel Calls | Notes |
|---|---|---|---|
| OpenAI (GPT-4) | Full Support | ✓ Yes | Best implementation, most reliable |
| Anthropic (Claude) | Full Support | ✓ Yes | Called "Tool Use" |
| Google (Gemini) | Full Support | ✓ Yes | Pro models only |
| Ollama (Llama 3) | Limited | ✗ No | Experimental, model-dependent |
| Mistral | Full Support | ✓ Yes | Large models only |
Best Practices
✓ Do
- • Write clear, detailed @JsonClassDescription
- • Return structured responses with success/error fields
- • Make functions idempotent when possible
- • Add timeout handling for external API calls
- • Use Optional/nullable for optional parameters
✗ Avoid
- • Vague descriptions ("does stuff")
- • Throwing unhandled exceptions
- • Too many functions (confuses the model)
- • Side effects without confirmation
- • Trusting model-provided user identity