{"record":{"id":"7a09fd0bb68019b6","repo":"spring-projects/spring-ai","slug":"method-must-return-either-completeresult-complete-7a09fd","errorCode":null,"errorMessage":"Method must return either CompleteResult, CompleteCompletion, List<String>, or String: ","messagePattern":"Method must return either CompleteResult, CompleteCompletion, List<String>, or String: ","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/complete/SyncMcpCompleteMethodCallback.java","lineNumber":140,"sourceCode":"\n\t\tthrow new IllegalArgumentException(\"Unsupported return type: \" + result.getClass().getName());\n\t}\n\n\t/**\n\t * Validates that the method return type is compatible with the complete callback.\n\t * @param method The method to validate\n\t * @throws IllegalArgumentException if the return type is not compatible\n\t */\n\t@Override\n\tprotected void validateReturnType(Method method) {\n\t\tClass<?> returnType = method.getReturnType();\n\n\t\tboolean validReturnType = CompleteResult.class.isAssignableFrom(returnType)\n\t\t\t\t|| CompleteCompletion.class.isAssignableFrom(returnType) || List.class.isAssignableFrom(returnType)\n\t\t\t\t|| String.class.isAssignableFrom(returnType);\n\n\t\tif (!validReturnType) {\n\t\t\tthrow new IllegalArgumentException(\n\t\t\t\t\t\"Method must return either CompleteResult, CompleteCompletion, List<String>, \" + \"or String: \"\n\t\t\t\t\t\t\t+ method.getName() + \" in \" + method.getDeclaringClass().getName() + \" returns \"\n\t\t\t\t\t\t\t+ returnType.getName());\n\t\t}\n\t}\n\n\t@Override\n\tprotected McpTransportContext resolveTransportContext(Object exchange) {\n\t\tif (exchange instanceof McpSyncServerExchange e) {\n\t\t\treturn e.transportContext();\n\t\t}\n\t\treturn null;\n\t}\n\n\t/**\n\t * Create a new builder.\n\t * @return A new builder instance\n\t */","sourceCodeStart":122,"sourceCodeEnd":158,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/complete/SyncMcpCompleteMethodCallback.java#L122-L158","documentation":"SyncMcpCompleteMethodCallback.validateReturnType throws this IllegalArgumentException at callback registration when a @McpComplete method's declared return type is not CompleteResult, CompleteCompletion, List, or String. This fails fast so invalid handlers are rejected before any request is served.","triggerScenarios":"Annotating a method with @McpComplete whose return type is void, a Map, a custom DTO, an array type, or any other non-supported class; validation runs in the constructor during annotation scanning.","commonSituations":"Adding @McpComplete to an existing method with an unrelated return type, typos in imports pulling in the wrong String/List, or migrating a tool method to a completion method without changing its signature.","solutions":["Change the method to return CompleteResult, CompleteCompletion, List<String>, or String","If the method computes something else, add a separate @McpComplete wrapper method that converts the result","Check the import: use java.util.List and java.lang.String, not project-local classes with the same names"],"exampleFix":"// before\n@McpComplete(promptName = \"code\")\npublic Map<String, Object> complete(String value) { return Map.of(); }\n// after\n@McpComplete(promptName = \"code\")\npublic List<String> complete(String value) { return List.of(\"java\", \"go\"); }","handlingStrategy":"validation","validationCode":"for (Method m : bean.getClass().getDeclaredMethods()) {\n    if (m.isAnnotationPresent(McpComplete.class)) {\n        Class<?> rt = m.getReturnType();\n        if (!(CompleteResult.class.isAssignableFrom(rt) || CompleteCompletion.class.isAssignableFrom(rt)\n              || List.class.isAssignableFrom(rt) || String.class.isAssignableFrom(rt)))\n            throw new IllegalStateException(\"bad @McpComplete return type on \" + m.getName());\n    }\n}","typeGuard":"static boolean hasValidCompleteReturnType(Method m) {\n    Class<?> rt = m.getReturnType();\n    return CompleteResult.class.isAssignableFrom(rt) || CompleteCompletion.class.isAssignableFrom(rt)\n        || List.class.isAssignableFrom(rt) || String.class.isAssignableFrom(rt);\n}","tryCatchPattern":"try { server.addCompletion(handler); }\ncatch (IllegalArgumentException e) { log.error(\"registration rejected: {}\", e.getMessage()); throw e; }","preventionTips":["Only annotate methods returning CompleteResult, CompleteCompletion, List<String>, or String","Run a startup self-check that scans @McpComplete methods before wiring the server","Keep completion handlers small and dedicated rather than reusing tool methods"],"tags":["java","mcp","completion","return-type","startup-validation"],"backgroundTag":"invalid-argument-value","analyzedSha":"98a7beda4f29d80a71c5837eb4053b03a93a46f7","analyzedAt":"2026-09-11T14:15:49.441Z","contentChangedAt":"2026-09-11T14:15:49.441Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}