{"record":{"id":"0f4efe6e0d1bd1f5","repo":"spring-projects/spring-ai","slug":"method-must-return-mono-createmessageresult-or-cr","errorCode":null,"errorMessage":"Method must return Mono<CreateMessageResult> or CreateMessageResult: {methodName} in {className} returns {returnTypeName}","messagePattern":"Method must return Mono<CreateMessageResult> or CreateMessageResult: (.+?) in (.+?) returns (.+?)","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/sampling/AsyncMcpSamplingMethodCallback.java","lineNumber":102,"sourceCode":"\t\t\t}\n\t\t}\n\t\tcatch (Exception e) {\n\t\t\treturn Mono\n\t\t\t\t.error(new McpSamplingMethodException(\"Error invoking sampling method: \" + this.method.getName(), e));\n\t\t}\n\t}\n\n\t/**\n\t * Validates that the method return type is compatible with the sampling 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\tif (!Mono.class.isAssignableFrom(returnType) && !CreateMessageResult.class.isAssignableFrom(returnType)) {\n\t\t\tthrow new IllegalArgumentException(\n\t\t\t\t\t\"Method must return Mono<CreateMessageResult> or CreateMessageResult: \" + method.getName() + \" in \"\n\t\t\t\t\t\t\t+ method.getDeclaringClass().getName() + \" returns \" + returnType.getName());\n\t\t}\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\n\t * otherwise\n\t */\n\t@Override\n\tprotected boolean isExchangeType(Class<?> paramType) {\n\t\t// No exchange type for sampling methods\n\t\treturn false;\n\t}\n\n\t/**","sourceCodeStart":84,"sourceCodeEnd":120,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/sampling/AsyncMcpSamplingMethodCallback.java#L84-L120","documentation":"AsyncMcpSamplingMethodCallback.validateReturnType throws IllegalArgumentException when a method registered as an async sampling callback does not return Mono<CreateMessageResult> or a CreateMessageResult subtype. The async callback path must be able to treat the outcome reactively or wrap it directly, so any other return type is rejected at callback construction time.","triggerScenarios":"Registering an @McpSampling-annotated method (or manually built AsyncMcpSamplingMethodCallback) whose signature returns e.g. String, void, or Flux<CreateMessageResult>, then building the callback.","commonSituations":"Writing a sampling handler method and forgetting the reactive wrapper; copying a sync callback method (returns CreateMessageResult-only sibling contract) into an async specification; upgrading from sync to async specs without changing return types.","solutions":["Change the method to return Mono<CreateMessageResult> (wrap a plain result with Mono.just)","Or return CreateMessageResult directly if a synchronous response is acceptable in this path","Verify with method.getReturnType() before registering"],"exampleFix":"// before\nCreateMessageResult sample(CreateMessageRequest req) { ... }\n// after\nMono<CreateMessageResult> sample(CreateMessageRequest req) {\n    return Mono.just(doSample(req));\n}","handlingStrategy":"validation","validationCode":"Class<?> rt = method.getReturnType();\nif (!Mono.class.isAssignableFrom(rt) && !CreateMessageResult.class.isAssignableFrom(rt)) {\n    throw new IllegalArgumentException(method + \" must return Mono<CreateMessageResult> or CreateMessageResult\");\n}","typeGuard":null,"tryCatchPattern":"try { builder.build(); } catch (IllegalArgumentException e) { log.error(\"Async sampling return type invalid: {}\", e.getMessage()); }","preventionTips":["Annotate async sampling methods with explicit Mono<CreateMessageResult> signatures","Add a startup-time reflection check over all registered sampling methods","Keep sync and async handler methods in separate classes to avoid signature drift"],"tags":["return-type","reactive","sampling"],"backgroundTag":"type-mismatch","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"}