{"record":{"id":"27f0b3857ba399e8","repo":"spring-projects/spring-ai","slug":"async-providers-don-t-support-imperative-non-reac","errorCode":null,"errorMessage":"ASYNC Providers don't support imperative (non-reactive) return types. Skipping method <method> with non-reactive return type <returnType>","messagePattern":"ASYNC Providers don't support imperative \\(non-reactive\\) return types\\. Skipping method <method> with non-reactive return type <returnType>","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/common/McpPredicates.java","lineNumber":61,"sourceCode":"\tpublic static boolean isUriTemplate(String uri) {\n\t\treturn URI_VARIABLE_PATTERN.matcher(uri).find();\n\t}\n\n\tpublic final static Predicate<Method> isReactiveReturnType = method -> Mono.class\n\t\t.isAssignableFrom(method.getReturnType()) || Flux.class.isAssignableFrom(method.getReturnType())\n\t\t\t|| Publisher.class.isAssignableFrom(method.getReturnType());\n\n\tpublic final static Predicate<Method> isNotReactiveReturnType = method -> !Mono.class\n\t\t.isAssignableFrom(method.getReturnType()) && !Flux.class.isAssignableFrom(method.getReturnType())\n\t\t\t&& !Publisher.class.isAssignableFrom(method.getReturnType());\n\n\tpublic static Predicate<Method> filterNonReactiveReturnTypeMethod() {\n\t\treturn method -> {\n\t\t\tif (isReactiveReturnType.test(method)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tif (logger.isWarnEnabled()) {\n\t\t\t\tlogger.warn(\"ASYNC Providers don't support imperative (non-reactive) return types. Skipping method \"\n\t\t\t\t\t\t+ method + \" with non-reactive return type \" + method.getReturnType());\n\t\t\t}\n\t\t\treturn false;\n\t\t};\n\t}\n\n\tpublic static Predicate<Method> filterReactiveReturnTypeMethod() {\n\t\treturn method -> {\n\t\t\tif (isNotReactiveReturnType.test(method)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tif (logger.isWarnEnabled()) {\n\t\t\t\tlogger.warn(\"SYNC Providers don't support reactive return types. Skipping method \" + method\n\t\t\t\t\t\t+ \" with reactive return type \" + method.getReturnType());\n\t\t\t}\n\t\t\treturn false;\n\t\t};\n\t}","sourceCodeStart":43,"sourceCodeEnd":79,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/common/McpPredicates.java#L43-L79","documentation":"When building an MCP server with ASYNC (reactive) providers, Spring AI inspects each annotated tool/resource/prompt method's return type. Methods with imperative (non-reactive) return types such as String or a plain object cannot be adapted to the async provider model, so they are skipped and a warning is logged instead of failing. The method is silently excluded from the resulting MCP server specification.","triggerScenarios":"Registering a @McpTool/@McpResource annotated bean with an ASYNC MCP server (McpServer.async(...) or async provider builder) where one or more annotated methods return plain types (String, POJOs) instead of Mono/Flux.","commonSituations":"Sharing the same annotated service class between a sync and an async server; migrating code from the sync starter to the async starter without changing method signatures; developers forgetting that async providers require reactive return types.","solutions":["Change the annotated method's return type to a reactive type (Mono<T> or Flux<T>) and wrap the existing logic, e.g. Mono.just(...)","Register the bean with a SYNC server/provider instead of ASYNC if reactive signatures are not desired","Split the annotated service so reactive-only methods live in the class registered with the async provider"],"exampleFix":"// before\n@McpTool(description = \"Greeting\")\npublic String greet(String name) { return \"Hello \" + name; }\n\n// after\n@McpTool(description = \"Greeting\")\npublic Mono<String> greet(String name) { return Mono.just(\"Hello \" + name); }","handlingStrategy":"validation","validationCode":"for (Method m : toolService.getClass().getDeclaredMethods()) {\n  if (m.isAnnotationPresent(McpTool.class) && !reactiveType.test(m.getReturnType())) {\n    throw new IllegalStateException(m + \" must return Mono/Flux for ASYNC providers\");\n  }\n}\n// reactiveType: Class<?> r -> r == Mono.class || r == Flux.class","typeGuard":"static boolean isReactiveReturn(Method m) {\n  Class<?> r = m.getReturnType();\n  return Mono.class.isAssignableFrom(r) || Flux.class.isAssignableFrom(r);\n}","tryCatchPattern":null,"preventionTips":["Use consistent return types (Mono/Flux) in services registered with async servers","Keep separate service classes for sync and async registrations","Watch startup logs for 'Skipping method ... non-reactive return type' warnings"],"tags":["spring-ai","mcp","reactive","return-type","async-provider"],"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"}