{"record":{"id":"dbbfebc5ffd34538","repo":"spring-projects/spring-ai","slug":"method-must-return-either-getpromptresult-list-pr-dbbfeb","errorCode":null,"errorMessage":"Method must return either GetPromptResult, List<PromptMessage>, List<String>, PromptMessage, or String: ${method} in ${declaringClass} returns ${returnType}","messagePattern":"Method must return either GetPromptResult, List<PromptMessage>, List<String>, PromptMessage, or String: (.+?) in (.+?) returns (.+?)","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/prompt/SyncMcpPromptMethodCallback.java","lineNumber":151,"sourceCode":"\t\t}\n\t}\n\n\t@Override\n\tprotected boolean isSupportedExchangeOrContextType(Class<?> paramType) {\n\t\treturn (McpSyncServerExchange.class.isAssignableFrom(paramType)\n\t\t\t\t|| McpTransportContext.class.isAssignableFrom(paramType));\n\t}\n\n\t@Override\n\tprotected void validateReturnType(Method method) {\n\t\tClass<?> returnType = method.getReturnType();\n\n\t\tboolean validReturnType = GetPromptResult.class.isAssignableFrom(returnType)\n\t\t\t\t|| List.class.isAssignableFrom(returnType) || PromptMessage.class.isAssignableFrom(returnType)\n\t\t\t\t|| String.class.isAssignableFrom(returnType);\n\n\t\tif (!validReturnType) {\n\t\t\tthrow new IllegalArgumentException(\"Method must return either GetPromptResult, List<PromptMessage>, \"\n\t\t\t\t\t+ \"List<String>, PromptMessage, or String: \" + method.getName() + \" in \"\n\t\t\t\t\t+ method.getDeclaringClass().getName() + \" returns \" + returnType.getName());\n\t\t}\n\t}\n\n\t/**\n\t * Create a new builder.\n\t * @return A new builder instance\n\t */\n\tpublic static Builder builder() {\n\t\treturn new Builder();\n\t}\n\n\t/**\n\t * Builder for creating SyncMcpPromptMethodCallback instances.\n\t * <p>\n\t * This builder provides a fluent API for constructing SyncMcpPromptMethodCallback\n\t * instances with the required parameters.","sourceCodeStart":133,"sourceCodeEnd":169,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/method/prompt/SyncMcpPromptMethodCallback.java#L133-L169","documentation":"A @McpPrompt-annotated method registered with the sync MCP prompt callback returns a type the framework cannot convert into a prompt result. The library validates the method's return type against a whitelist (GetPromptResult, List, PromptMessage, String) and throws IllegalArgumentException when none match. This fails fast at registration/startup rather than at request time.","triggerScenarios":"Registering a prompt method (e.g. via McpServerFeatures or the annotations auto-config) whose return type is a POJO, CompletableFuture, Optional, custom wrapper, or primitive not on the allowed list; validateReturnType checks GetPromptResult/List/PromptMessage/String assignability and throws otherwise.","commonSituations":"Returning a custom DTO class directly; forgetting to wrap messages in PromptMessage; returning reactor Mono/Flux in a sync callback; copy-pasting a method that worked as a tool (where return types are broader) into a @McpPrompt method.","solutions":["Change the method's return type to GetPromptResult, List<PromptMessage>, List<String>, PromptMessage, or String.","Wrap custom message data in PromptMessage with role and content (TextContent or EmbeddedResource).","If the result is async/reactive, move to the async callback variant or block/convert to a sync type before returning.","For lists of arbitrary strings, return List<String> which the framework converts into message content."],"exampleFix":"// before\n@McpPrompt(description = \"greet\")\npublic GreetingDto greet(String name) {\n    return new GreetingDto(\"Hello \" + name);\n}\n\n// after\n@McpPrompt(description = \"greet\")\npublic String greet(String name) {\n    return \"Hello \" + name;\n}","handlingStrategy":"validation","validationCode":"static boolean hasValidPromptReturnType(Method m) {\n    Class<?> r = m.getReturnType();\n    return GetPromptResult.class.isAssignableFrom(r) || List.class.isAssignableFrom(r)\n        || PromptMessage.class.isAssignableFrom(r) || String.class.isAssignableFrom(r);\n}\n// assert hasValidPromptReturnType(promptMethod) before registration;","typeGuard":null,"tryCatchPattern":"try {\n    server.addPrompt(promptSpec);\n} catch (IllegalArgumentException e) {\n    log.error(\"Prompt method signature invalid: {}\", e.getMessage());\n    throw new IllegalStateException(\"Fix @McpPrompt return type\", e);\n}","preventionTips":["Only return whitelisted types (GetPromptResult, List<PromptMessage>, List<String>, PromptMessage, String) from @McpPrompt methods.","Write an arch-unit or unit test that scans all @McpPrompt methods and asserts return-type validity at CI time.","Do not reuse tool method signatures as prompt methods without reviewing the return type."],"tags":["mcp","java","invalid-return-type","annotation-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"}