apache/dubbo · error · RpcException
INTERNAL_ERROR
INTERNAL_ERROR
Error message
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}. What it means
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).
Source
Thrown at dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/filter/FilterChainBuilder.java:356
@Override
public boolean isAvailable() {
return originalInvoker.isAvailable();
}
@Override
public Result invoke(Invocation invocation) throws RpcException {
Result asyncResult;
try {
InvocationProfilerUtils.enterDetailProfiler(
invocation, () -> "Filter " + filter.getClass().getName() + " invoke.");
asyncResult = filter.invoke(nextNode, invocation);
if (!(asyncResult instanceof AsyncRpcResult)) {
String msg =
"The result of filter invocation must be AsyncRpcResult. (If you want to recreate a result, please use AsyncRpcResult.newDefaultAsyncResult.) "
+ "Filter class: " + filter.getClass().getName() + ". Result class: "
+ asyncResult.getClass().getName() + ".";
LOGGER.error(INTERNAL_ERROR, "", "", msg);
throw new RpcException(msg);
}
} catch (Exception e) {
InvocationProfilerUtils.releaseDetailProfiler(invocation);
if (filter instanceof ListenableFilter) {
ListenableFilter listenableFilter = ((ListenableFilter) filter);
try {
Filter.Listener listener = listenableFilter.listener(invocation);
if (listener != null) {
listener.onError(e, originalInvoker, invocation);
}
} finally {
listenableFilter.removeListener(invocation);
}
} else if (filter instanceof FILTER.Listener) {
FILTER.Listener listener = (FILTER.Listener) filter;
listener.onError(e, originalInvoker, invocation);
}
throw e;View on GitHub (pinned to 3a3043227f)
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.
Example fix
// before (broken): custom filter returns a plain result
public Result invoke(Invoker<?> invoker, Invocation inv) {
if (cached != null) return new AppResponse(cached); // not AsyncRpcResult -> throws
return invoker.invoke(inv);
}
// after
public Result invoke(Invoker<?> invoker, Invocation inv) {
if (cached != null) return AsyncRpcResult.newDefaultAsyncResult(cached);
return invoker.invoke(inv);
} Defensive patterns
Strategy: type-guard
Validate before calling
// In a custom filter, always produce an AsyncRpcResult
public Result invoke(Invoker<?> invoker, Invocation inv) {
Result r = invoker.invoke(inv);
if (!(r instanceof AsyncRpcResult) && shouldTransform(r)) {
return AsyncRpcResult.newDefaultAsyncResult(r.getValue());
}
return r;
} Type guard
// Type guard for filter results
static boolean isAsyncResult(Result r) {
return r instanceof AsyncRpcResult;
} Try / catch
try {
return filter.invoke(nextNode, invocation);
} catch (RpcException e) {
if (e.getMessage().contains("must be AsyncRpcResult")) {
// filter returned a wrong type; wrap/replace with AsyncRpcResult
return AsyncRpcResult.newDefaultAsyncResult(e);
}
throw e;
} Prevention
- 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.
When it happens
Trigger: 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(...).
Common situations: 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.
Related errors
- INTERNAL_ERROR
- register bean failed! name=${name}, type=${className}
- Property [{}] not found.
- CAN NOT convert String(%s) to char! when convert String to c
- Failed to parse date {value} by format {DATE_FORMAT}, cause:
AI-assisted analysis of apache/dubbo@3a3043227f (2026-08-14).
Data as JSON: /api/errors/8ca96c1700d85bc3.
Report an issue: GitHub.