spring-projects/spring-ai · error · IllegalArgumentException

Failed to instantiate ToolCallResultConverter:

Error message

Failed to instantiate ToolCallResultConverter: 

What it means

Thrown when the ToolCallResultConverter class declared on a @Tool method's annotation cannot be instantiated: its no-arg constructor is missing or the newInstance call throws (abstract class, interface, constructor exception, inaccessible class).

Source

Thrown at spring-ai-model/src/main/java/org/springframework/ai/tool/support/ToolUtils.java:102

	public static boolean getToolReturnDirect(Method method) {
		Assert.notNull(method, "method cannot be null");
		var tool = AnnotatedElementUtils.findMergedAnnotation(method, Tool.class);
		return tool != null && tool.returnDirect();
	}

	public static ToolCallResultConverter getToolCallResultConverter(Method method) {
		Assert.notNull(method, "method cannot be null");
		var tool = AnnotatedElementUtils.findMergedAnnotation(method, Tool.class);
		if (tool == null) {
			return new DefaultToolCallResultConverter();
		}
		var type = tool.resultConverter();
		try {
			return type.getDeclaredConstructor().newInstance();
		}
		catch (Exception e) {
			throw new IllegalArgumentException("Failed to instantiate ToolCallResultConverter: " + type, e);
		}
	}

	public static List<String> getDuplicateToolNames(List<ToolCallback> toolCallbacks) {
		Assert.notNull(toolCallbacks, "toolCallbacks cannot be null");
		return toolCallbacks.stream()
			.collect(Collectors.groupingBy(toolCallback -> toolCallback.getToolDefinition().name(),
					Collectors.counting()))
			.entrySet()
			.stream()
			.filter(entry -> entry.getValue() > 1)
			.map(Map.Entry::getKey)
			.toList();
	}

	public static List<String> getDuplicateToolNames(ToolCallback... toolCallbacks) {
		Assert.notNull(toolCallbacks, "toolCallbacks cannot be null");
		return getDuplicateToolNames(Arrays.asList(toolCallbacks));

View on GitHub (pinned to 98a7beda4f)

Solutions

  1. Give the converter class a public no-argument constructor
  2. Ensure the converter is a concrete (non-abstract) static class
  3. Remove or fix side effects in the constructor that can throw
  4. Check class/module accessibility (public class, exported package, non-anonymous inner class)

Example fix

// before
class MyConverter {
    MyConverter(ObjectMapper mapper) { ... }
}
// after
class MyConverter implements ToolCallResultConverter {
    public MyConverter() { }
    public String convert(@Nullable Object result, @Nullable Type returnType) { ... }
}
Defensive patterns

Strategy: validation

Validate before calling

try {
    converterType.getDeclaredConstructor().newInstance();
} catch (Exception e) {
    throw new IllegalStateException(converterType + " needs an accessible public no-arg constructor", e);
}

Try / catch

try { tool.call(args); } catch (IllegalArgumentException e) {
    if (e.getMessage().startsWith("Failed to instantiate ToolCallResultConverter")) { /* fix converter class */ }
}

Prevention

When it happens

Trigger: Setting ToolCallResultConverter.class in tool annotations/options to a class that (a) has no public no-arg constructor, (b) is abstract/an interface, (c) throws in its constructor, or (d) is not accessible from the caller's classloader/module.

Common situations: Writing a custom result converter with constructor arguments; passing a nested class without declaring it static; converter with initialization logic that fails (missing config); passing the annotation's default placeholder incorrectly.

Related errors


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