spring-projects/spring-ai · error · IllegalStateException

No ToolCallback found for tool name: %s

Error message

No ToolCallback found for tool name: %s

What it means

During tool execution, DefaultToolCallingManager.executeToolCall resolves the ToolCallback matching the tool name the model requested. If no registered callback has that name, it throws IllegalStateException. This typically means the model changed the tool name relative to what was registered, or the tool was never registered in the options.

Source

Thrown at spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingManager.java:296

							+ ". Using empty JSON object as default.");
				}
				finalToolInputArguments = "{}";
			}
			else {
				finalToolInputArguments = toolInputArguments;
			}

			ToolCallback toolCallback = toolCallbacks.stream()
				.filter(tool -> toolName.equals(tool.getToolDefinition().name()))
				.findFirst()
				.orElseGet(() -> this.resolutionFallbackEnabled ? this.toolCallbackResolver.resolve(toolName) : null);

			if (toolCallback == null) {
				if (logger.isWarnEnabled()) {
					logger.warn(POSSIBLE_LLM_TOOL_NAME_CHANGE_WARNING_START + toolName
							+ POSSIBLE_LLM_TOOL_NAME_CHANGE_WARNING_END);
				}
				throw new IllegalStateException("No ToolCallback found for tool name: " + toolName);
			}

			if (returnDirect == null) {
				returnDirect = toolCallback.getToolMetadata().returnDirect();
			}
			else {
				returnDirect = returnDirect && toolCallback.getToolMetadata().returnDirect();
			}

			// In streaming/reactive mode the parent observation is propagated through the
			// Reactor context captured in ToolCallReactiveContextHolder. In blocking mode
			// that holder is never populated, so fall back to the observation currently
			// in scope on the calling thread to keep the observation hierarchy intact.
			Observation parent = ToolCallReactiveContextHolder.getContext()
				.getOrDefault(ObservationThreadLocalAccessor.KEY, this.observationRegistry.getCurrentObservation());

			ToolCallingObservationContext observationContext = ToolCallingObservationContext.builder()
				.toolDefinition(toolCallback.getToolDefinition())

View on GitHub (pinned to 98a7beda4f)

Solutions

  1. Check the manager's WARN log (POSSIBLE_LLM_TOOL_NAME_CHANGE_WARNING) and compare the requested name against your registered callback names
  2. Ensure all intended ToolCallbacks are set on ToolCallingChatOptions (toolCallbacks(...) / tools(...)) for the exact request
  3. Register a default/fallback ToolCallback or use a ToolCallbackResolver so unknown names resolve instead of throwing
  4. Simplify tool names (avoid special characters, keep them short) to prevent LLM name mutation

Example fix

// before
ChatOptions options = ToolCallingChatOptions.builder().build(); // tools omitted
// after
ChatOptions options = ToolCallingChatOptions.builder()
    .toolCallbacks(weatherTool, stockTool)
    .build();
Defensive patterns

Strategy: validation

Validate before calling

// Verify the tool names you register are what the model will see
Set<String> registered = options.getToolCallbacks().stream()
    .map(ToolCallback::getName).collect(Collectors.toSet());
// After the model replies, before manual execution:
// assistantMessage.getToolCalls().stream().allMatch(tc -> registered.contains(tc.name()))

Try / catch

try {
    return chatModel.call(prompt);
} catch (IllegalStateException e) {
    if (e.getMessage() != null && e.getMessage().startsWith("No ToolCallback found for tool name")) {
        logger.warn("Model requested unknown tool; re-prompting with tool list");
        return chatModel.call(rePromptWithExplicitToolList);
    }
    throw e;
}

Prevention

When it happens

Trigger: Calling a ChatModel with tool callbacks where the model's assistant message contains a tool call whose name has no matching ToolCallback in ToolCallingChatOptions.getToolCallbacks(); executeToolCall's lookup (including callback-resolver fallback) returns null.

Common situations: Model mangling or inventing tool names (sanitization, truncation, special-character handling), registering tools on the request but the model hallucinating a different name, stale options that dropped callbacks after an upgrade, or misconfigured function names in prompt/context.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11). Data as JSON: /api/errors/4c703a47d7c08e98. Report an issue: GitHub.