{"record":{"id":"63a672e176e2b923","repo":"github/copilot-sdk","slug":"fromclass-requires-all-copilottool-methods-to-b","errorCode":null,"errorMessage":"fromClass() requires all @CopilotTool methods to be static, but found instance methods: + instanceMethods + . Use fromObject(new  + clazz.getSimpleName() + ()) instead.","messagePattern":"fromClass\\(\\) requires all @CopilotTool methods to be static, but found instance methods: \\+ instanceMethods \\+ \\. Use fromObject\\(new  \\+ clazz\\.getSimpleName\\(\\) \\+ \\(\\)\\) instead\\.","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"java/sdk/src/main/java/com/github/copilot/rpc/ToolDefinition.java","lineNumber":320,"sourceCode":"     *\n     * @param clazz\n     *            the class containing static {@code @CopilotTool}-annotated methods\n     * @return list of tool definitions with working invocation handlers\n     * @throws IllegalStateException\n     *             if the generated {@code $$CopilotToolMeta} class is not found\n     *             (annotation processor did not run)\n     * @since 1.0.6\n     */\n    @CopilotExperimental\n    public static List<ToolDefinition> fromClass(Class<?> clazz) {\n        if (clazz == null) {\n            throw new IllegalArgumentException(\"clazz must not be null\");\n        }\n        List<String> instanceMethods = Arrays.stream(clazz.getDeclaredMethods())\n                .filter(m -> m.isAnnotationPresent(com.github.copilot.tool.CopilotTool.class))\n                .filter(m -> !Modifier.isStatic(m.getModifiers())).map(Method::getName).collect(Collectors.toList());\n        if (!instanceMethods.isEmpty()) {\n            throw new IllegalArgumentException(\n                    \"fromClass() requires all @CopilotTool methods to be static, but found instance methods: \"\n                            + instanceMethods + \". Use fromObject(new \" + clazz.getSimpleName() + \"()) instead.\");\n        }\n        return loadDefinitions(clazz, null);\n    }\n\n    // ------------------------------------------------------------------\n    // Fluent copy-style modifier methods for lambda-defined tools\n    // ------------------------------------------------------------------\n\n    /**\n     * Returns a copy with the {@code overridesBuiltInTool} flag set.\n     *\n     * @param value\n     *            {@code true} to indicate this tool intentionally overrides a\n     *            built-in CLI tool with the same name\n     * @return a new {@code ToolDefinition} with the flag applied\n     * @since 1.0.6","sourceCodeStart":302,"sourceCodeEnd":338,"githubUrl":"https://github.com/github/copilot-sdk/blob/cd8cf15dc3f9e762615790aaed0a771a0f392755/java/sdk/src/main/java/com/github/copilot/rpc/ToolDefinition.java#L302-L338","documentation":"ToolDefinition.fromClass() only supports static @CopilotTool methods because it has no object instance to invoke methods on. If any @CopilotTool-annotated method on the class is an instance method, registration cannot proceed and this IllegalArgumentException is thrown listing the offending method names. The library explicitly directs you to use fromObject(new Clazz()) instead, which provides an instance for dispatch.","triggerScenarios":"Calling ToolDefinition.fromClass(SomeClass.class) where the class declares one or more non-static methods annotated with @CopilotTool (e.g. public List<String> search(...) instead of public static List<String> search(...)).","commonSituations":"Converting an existing service class to Copilot tools by adding @CopilotTool to existing instance methods without making them static; forgetting the 'static' modifier when writing a new tool class intended for fromClass(); switching registration from fromObject to fromClass for convenience without moving methods to static.","solutions":["Make every @CopilotTool method static in the class passed to fromClass().","Alternatively register an instance instead: ToolDefinition.fromObject(new SomeClass()), which supports instance methods.","If the methods need injected state, use fromObject with a constructor-initialized instance rather than forcing statics."],"exampleFix":"// before\nclass SearchTools {\n    @CopilotTool(name = \"search\")\n    public String search(String q) { return doSearch(q); } // instance method\n}\nToolDefinition.fromClass(SearchTools.class); // throws\n\n// after\nclass SearchTools {\n    @CopilotTool(name = \"search\")\n    public static String search(String q) { return doSearch(q); }\n}\nToolDefinition.fromClass(SearchTools.class); // ok\n// or: ToolDefinition.fromObject(new SearchTools());","handlingStrategy":"validation","validationCode":"boolean ok = Arrays.stream(Tools.class.getDeclaredMethods())\n    .filter(m -> m.isAnnotationPresent(com.github.copilot.tool.CopilotTool.class))\n    .allMatch(m -> Modifier.isStatic(m.getModifiers()));\nif (!ok) throw new IllegalStateException(\"all @CopilotTool methods must be static for fromClass\");","typeGuard":null,"tryCatchPattern":"try { defs = ToolDefinition.fromClass(Tools.class); } catch (IllegalArgumentException e) { log.error(\"tool registration failed: {}\", e.getMessage()); throw e; }","preventionTips":["Make @CopilotTool methods static by default when designing fromClass-based tool classes.","Use fromObject(new Clazz()) whenever handlers need instance state.","Add an architecture/unit test asserting all @CopilotTool methods in fromClass-registered classes are static."],"tags":["java","reflection","annotation-processing","tool-registration"],"backgroundTag":"invalid-argument-value","analyzedSha":"cd8cf15dc3f9e762615790aaed0a771a0f392755","analyzedAt":"2026-09-09T18:32:31.973Z","contentChangedAt":"2026-09-09T18:32:31.973Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}