{"record":{"id":"cf397e2b5ed4e950","repo":"spring-projects/spring-ai","slug":"method-must-return-either-completeresult-complete-cf397e","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/SyncStatelessMcpCompleteMethodCallback.java","lineNumber":139,"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 context) {\n\t\tif (context instanceof McpTransportContext c) {\n\t\t\treturn c;\n\t\t}\n\t\treturn null;\n\t}\n\n\t/**\n\t * Checks if a parameter type is compatible with the exchange type.\n\t * @param paramType The parameter type to check\n\t * @return true if the parameter type is compatible with the exchange type, false","sourceCodeStart":121,"sourceCodeEnd":157,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/complete/SyncStatelessMcpCompleteMethodCallback.java#L121-L157","documentation":"SyncStatelessMcpCompleteMethodCallback.validateReturnType throws this IllegalArgumentException at registration when a @McpComplete method's declared return type is not CompleteResult, CompleteCompletion, List, or String. This startup-time validation prevents invalid handlers from ever being registered on the server.","triggerScenarios":"Annotating a method returning void, Map, custom DTO, or array with @McpComplete and building the SyncStatelessMcpCompleteMethodCallback during annotation scanning.","commonSituations":"Reusing a tool method as a completion handler without changing its signature, or a refactoring that changed the return type after registration code was written.","solutions":["Change the return type to CompleteResult, CompleteCompletion, List<String>, or String","Create a dedicated @McpComplete method that adapts the existing method's output","Verify imports so List/String resolve to java.util.List / java.lang.String"],"exampleFix":"// before\n@McpComplete(promptName = \"p\")\npublic void complete(String v) { }\n// after\n@McpComplete(promptName = \"p\")\npublic List<String> complete(String v) { return List.of(); }","handlingStrategy":"validation","validationCode":"Class<?> rt = method.getReturnType();\nif (!(CompleteResult.class.isAssignableFrom(rt) || CompleteCompletion.class.isAssignableFrom(rt)\n      || List.class.isAssignableFrom(rt) || String.class.isAssignableFrom(rt)))\n    throw new IllegalStateException(\"@McpComplete method must return CompleteResult/CompleteCompletion/List<String>/String\");","typeGuard":"static boolean isCompleteCompatible(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 { registry.register(statelessCallback); }\ncatch (IllegalArgumentException e) { log.error(\"invalid @McpComplete signature: {}\", e.getMessage()); throw new BeanInitializationException(e.getMessage(), e); }","preventionTips":["Validate return types in a startup test that scans all @McpComplete methods","Never return void or custom DTOs from completion handlers","Keep imports correct (java.util.List)"],"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"}