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
- Give the converter class a public no-argument constructor
- Ensure the converter is a concrete (non-abstract) static class
- Remove or fix side effects in the constructor that can throw
- 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
- Write converters as public static classes with only a no-arg constructor
- Unit-test converter instantiation for every custom ToolCallResultConverter
- Keep constructor work minimal and non-throwing
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
- instantiation failed
- ClassNotFoundException (wrapped RuntimeException)
- This is a utility class and cannot be instantiated
- Failed to extract record field types
- Could not access method:
AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11).
Data as JSON: /api/errors/c667965d25f4b0b4.
Report an issue: GitHub.