{"record":{"id":"618a310ad70b2050","repo":"spring-projects/spring-ai","slug":"toolcontext-is-required-by-the-method-as-an-argume","errorCode":null,"errorMessage":"ToolContext is required by the method as an argument","messagePattern":"ToolContext is required by the method as an argument","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"spring-ai-model/src/main/java/org/springframework/ai/tool/method/MethodToolCallback.java","lineNumber":130,"sourceCode":"\t\tObject[] methodArguments = this.buildMethodArguments(toolArguments, toolContext);\n\n\t\tObject result = this.callMethod(methodArguments);\n\n\t\tif (logger.isDebugEnabled()) {\n\t\t\tlogger.debug(\"Successful execution of tool: \" + this.toolDefinition.name());\n\t\t}\n\n\t\tType returnType = this.toolMethod.getGenericReturnType();\n\n\t\treturn this.toolCallResultConverter.convert(result, returnType);\n\t}\n\n\tprivate void validateToolContextSupport(@Nullable ToolContext toolContext) {\n\t\tvar isNonEmptyToolContextProvided = toolContext != null && !CollectionUtils.isEmpty(toolContext.getContext());\n\t\tvar isToolContextAcceptedByMethod = Stream.of(this.toolMethod.getParameterTypes())\n\t\t\t.anyMatch(type -> ClassUtils.isAssignable(ToolContext.class, type));\n\t\tif (isToolContextAcceptedByMethod && !isNonEmptyToolContextProvided) {\n\t\t\tthrow new IllegalArgumentException(\"ToolContext is required by the method as an argument\");\n\t\t}\n\t}\n\n\tprivate @Nullable Map<String, Object> extractToolArguments(String toolInput) {\n\t\ttry {\n\t\t\treturn jsonHelper.fromJson(toolInput, new ParameterizedTypeReference<>() {\n\t\t\t});\n\t\t}\n\t\tcatch (Exception ex) {\n\t\t\tlogger.warn(\"Conversion from JSON failed\", ex);\n\t\t\tThrowable cause = (ex.getCause() instanceof JacksonException) ? ex.getCause() : ex;\n\t\t\tthrow new ToolExecutionException(this.getToolDefinition(), cause);\n\t\t}\n\t}\n\n\t// Based on the implementation in MethodToolCallback.\n\t@SuppressWarnings(\"null\")\n\tprivate Object[] buildMethodArguments(Map<String, Object> toolInputArguments, @Nullable ToolContext toolContext) {","sourceCodeStart":112,"sourceCodeEnd":148,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/spring-ai-model/src/main/java/org/springframework/ai/tool/method/MethodToolCallback.java#L112-L148","documentation":"MethodToolCallback.call() validates that if the target @Tool method declares a ToolContext parameter, a non-empty ToolContext was actually provided at call time. If the method accepts ToolContext but the caller passed null or an empty context, it throws an IllegalArgumentException, because the tool would otherwise run without required contextual data.","triggerScenarios":"Invoking toolCallbacks with a null toolContext or new ToolContext(Map.of()) when the @Tool method signature is e.g. void run(String q, ToolContext ctx); calling the tool directly via chatClient tools without passing context.","commonSituations":"Tools designed for tenant/user IDs passed via ToolContext being invoked from tests or from ChatClient calls that omit .toolContext(...); framework-internal calls that never supply context.","solutions":["Pass a non-empty ToolContext when calling: new ToolCallback invocation with .toolContext(Map.of(\"key\", value)) or methodContext on ChatClient.","If context is genuinely optional, remove the ToolContext parameter from the @Tool method and use closure/field state instead.","Guard call sites with a check that the context map is non-empty before invoking the tool."],"exampleFix":"// before\nString result = toolCallback.call(\"{\\\"q\\\":\\\"hi\\\"}\"); // no context, method requires ToolContext\n// after\nString result = toolCallback.call(\"{\\\"q\\\":\\\"hi\\\"}\", new ToolContext(Map.of(\"tenantId\", \"acme\")));","handlingStrategy":"validation","validationCode":"if (toolContext == null || toolContext.getContext().isEmpty()) {\n    throw new IllegalArgumentException(\"This tool requires a non-empty ToolContext\");\n}","typeGuard":"boolean contextSatisfied = toolContext != null && !toolContext.getContext().isEmpty();","tryCatchPattern":"try { result = toolCallback.call(input, toolContext); } catch (IllegalArgumentException e) { if (e.getMessage().contains(\"ToolContext is required\")) { toolContext = new ToolContext(defaultContext()); result = toolCallback.call(input, toolContext); } else throw e; }","preventionTips":["Always pass .toolContext(...) at ChatClient call sites whose tools declare ToolContext parameters.","Document required context keys alongside each @Tool method.","Add integration tests invoking every tool with representative context."],"tags":["tool-context","validation","spring-ai","tools"],"backgroundTag":"missing-required-argument","analyzedSha":"98a7beda4f29d80a71c5837eb4053b03a93a46f7","analyzedAt":"2026-09-11T14:15:49.441Z","contentChangedAt":"2026-09-11T14:15:49.441Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}