{"record":{"id":"8ca96c1700d85bc3","repo":"apache/dubbo","slug":"internal-error-8ca96c","errorCode":"INTERNAL_ERROR","errorMessage":"The result of filter invocation must be AsyncRpcResult. (If you want to recreate a result, please use AsyncRpcResult.newDefaultActionResult.) Filter class: {filterClass}. Result class: {resultClass}.","messagePattern":"The result of filter invocation must be AsyncRpcResult\\. \\(If you want to recreate a result, please use AsyncRpcResult\\.newDefaultActionResult\\.\\) Filter class: (.+?)\\. Result class: (.+?)\\.","errorType":"exception","errorClass":"RpcException","httpStatus":null,"severity":"error","filePath":"dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/filter/FilterChainBuilder.java","lineNumber":356,"sourceCode":"        @Override\n        public boolean isAvailable() {\n            return originalInvoker.isAvailable();\n        }\n\n        @Override\n        public Result invoke(Invocation invocation) throws RpcException {\n            Result asyncResult;\n            try {\n                InvocationProfilerUtils.enterDetailProfiler(\n                        invocation, () -> \"Filter \" + filter.getClass().getName() + \" invoke.\");\n                asyncResult = filter.invoke(nextNode, invocation);\n                if (!(asyncResult instanceof AsyncRpcResult)) {\n                    String msg =\n                            \"The result of filter invocation must be AsyncRpcResult. (If you want to recreate a result, please use AsyncRpcResult.newDefaultAsyncResult.) \"\n                                    + \"Filter class: \" + filter.getClass().getName() + \". Result class: \"\n                                    + asyncResult.getClass().getName() + \".\";\n                    LOGGER.error(INTERNAL_ERROR, \"\", \"\", msg);\n                    throw new RpcException(msg);\n                }\n            } catch (Exception e) {\n                InvocationProfilerUtils.releaseDetailProfiler(invocation);\n                if (filter instanceof ListenableFilter) {\n                    ListenableFilter listenableFilter = ((ListenableFilter) filter);\n                    try {\n                        Filter.Listener listener = listenableFilter.listener(invocation);\n                        if (listener != null) {\n                            listener.onError(e, originalInvoker, invocation);\n                        }\n                    } finally {\n                        listenableFilter.removeListener(invocation);\n                    }\n                } else if (filter instanceof FILTER.Listener) {\n                    FILTER.Listener listener = (FILTER.Listener) filter;\n                    listener.onError(e, originalInvoker, invocation);\n                }\n                throw e;","sourceCodeStart":338,"sourceCodeEnd":374,"githubUrl":"https://github.com/apache/dubbo/blob/3a3043227f5571d25eb2889de5bca22f2914843b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/filter/FilterChainBuilder.java#L338-L374","documentation":"Thrown by the cluster FilterChainBuilder node when a Filter.invoke(...) returns a Result that is not an AsyncRpcResult. Dubbo's filter chain is built around async results: every filter (cluster-scoped or provider/consumer-scoped) must return an AsyncRpcResult. Returning any other Result type (e.g., a raw AppResponse or a custom Result) breaks the async contract and is rejected (logged as INTERNAL_ERROR).","triggerScenarios":"A custom Filter (or a filter extending ListenableFilter / implementing Filter with an onResponse callback) whose invoke() constructs and returns its own Result via 'new AppResponse(...)' or 'new RpcResult(...)' instead of going through AsyncRpcResult.newDefaultAsyncResult(...).","commonSituations":"Writing a custom Dubbo Filter that short-circuits (e.g., a cache filter returning a cached value) and building the Result manually; porting a pre-2.7 filter to a newer Dubbo version where AsyncRpcResult is mandatory; a filter that wraps/swallows the downstream result incorrectly.","solutions":["Return AsyncRpcResult from filter.invoke(); when synthesizing a result use AsyncRpcResult.newDefaultAsyncResult(value) or newDefaultAsyncResult(exception).","Pass through the downstream result unchanged unless you intentionally replace it, and when replacing always produce an AsyncRpcResult.","Audit custom filters against the post-2.7 Filter contract; the message names the offending filter class."],"exampleFix":"// before (broken): custom filter returns a plain result\npublic Result invoke(Invoker<?> invoker, Invocation inv) {\n    if (cached != null) return new AppResponse(cached); // not AsyncRpcResult -> throws\n    return invoker.invoke(inv);\n}\n\n// after\npublic Result invoke(Invoker<?> invoker, Invocation inv) {\n    if (cached != null) return AsyncRpcResult.newDefaultAsyncResult(cached);\n    return invoker.invoke(inv);\n}","handlingStrategy":"type-guard","validationCode":"// In a custom filter, always produce an AsyncRpcResult\npublic Result invoke(Invoker<?> invoker, Invocation inv) {\n    Result r = invoker.invoke(inv);\n    if (!(r instanceof AsyncRpcResult) && shouldTransform(r)) {\n        return AsyncRpcResult.newDefaultAsyncResult(r.getValue());\n    }\n    return r;\n}","typeGuard":"// Type guard for filter results\nstatic boolean isAsyncResult(Result r) {\n    return r instanceof AsyncRpcResult;\n}","tryCatchPattern":"try {\n    return filter.invoke(nextNode, invocation);\n} catch (RpcException e) {\n    if (e.getMessage().contains(\"must be AsyncRpcResult\")) {\n        // filter returned a wrong type; wrap/replace with AsyncRpcResult\n        return AsyncRpcResult.newDefaultAsyncResult(e);\n    }\n    throw e;\n}","preventionTips":["Always return AsyncRpcResult from Filter.invoke; use AsyncRpcResult.newDefaultAsyncResult(...) to synthesize.","Do not construct AppResponse/RpcResult directly in a filter.","Add a unit test asserting the filter's return type is AsyncRpcResult."],"tags":["filter","async","rpc","internal-error","extension"],"backgroundTag":null,"analyzedSha":"3a3043227f5571d25eb2889de5bca22f2914843b","analyzedAt":"2026-08-14T00:43:19.853Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}