{"record":{"id":"05464081ac0164ed","repo":"spring-projects/spring-ai","slug":"method-must-return-either-completeresult-complete-054640","errorCode":null,"errorMessage":"Method must return either CompleteResult, CompleteCompletion, List<String>, String, or Mono<T>: {method} in {class} returns {returnType}","messagePattern":"Method must return either CompleteResult, CompleteCompletion, List<String>, String, or Mono<T>: (.+?) in (.+?) returns (.+?)","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/complete/AsyncStatelessMcpCompleteMethodCallback.java","lineNumber":152,"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) || Mono.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>, \"\n\t\t\t\t\t\t\t+ \"String, or Mono<T>: \" + method.getName() + \" in \" + method.getDeclaringClass().getName()\n\t\t\t\t\t\t\t+ \" returns \" + 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":134,"sourceCodeEnd":170,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/complete/AsyncStatelessMcpCompleteMethodCallback.java#L134-L170","documentation":"At callback construction, validateReturnType checks the annotated method's declared return type against the supported set: CompleteResult, CompleteCompletion, List, String, or Mono. Any other declared return type fails fast with this IllegalArgumentException naming the method, class, and offending return type. (Note: List is accepted without checking its element type here, so bad element types surface later at runtime.)","triggerScenarios":"Registering an @McpComplete method whose declared return type is, e.g., Map<String,Object>, CompleteCompletion[] (array), Flux<String>, void, or a custom DTO class.","commonSituations":"Using Flux instead of Mono (the wrong reactive type); returning a CompletableFuture; returning void from a completion handler; a refactoring that changed the return type without updating the annotation contract.","solutions":["Change the method's declared return type to one of: CompleteResult, CompleteCompletion, List<String>, String, or Mono<T> where T is one of the convertible types","Replace Flux<String> with Mono<List<String>> (collectList())","If returning CompletableFuture, switch to Mono.fromFuture(...)","Check the method, class, and returnType names in the message to locate the offending signature"],"exampleFix":"// before\npublic Flux<String> complete(String arg) { ... }\n// after\npublic Mono<List<String>> complete(String arg) {\n    return fluxSuggestions(arg).collectList();\n}","handlingStrategy":"validation","validationCode":"Class<?> rt = method.getReturnType();\nboolean ok = CompleteResult.class.isAssignableFrom(rt) || CompleteCompletion.class.isAssignableFrom(rt)\n    || List.class.isAssignableFrom(rt) || String.class.isAssignableFrom(rt)\n    || Mono.class.isAssignableFrom(rt);\nif (!ok) throw new IllegalArgumentException(method + \" has unsupported return type \" + rt);","typeGuard":null,"tryCatchPattern":"try {\n    registerCompletion(bean, method);\n} catch (IllegalArgumentException e) {\n    log.error(\"Completion method rejected at registration: {}\", e.getMessage());\n    throw e; // fail fast at startup, not at request time\n}","preventionTips":["Validate return types in an application-startup test that registers every @McpComplete bean","Use only the documented return types: CompleteResult, CompleteCompletion, List<String>, String, Mono<T>","Remember Flux is not accepted — use Mono<List<T>> via collectList()","Keep @McpComplete method signatures simple; do complex mapping in helper methods"],"tags":["mcp","completion","validation","java"],"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-14T05:17:10.506Z"}