{"record":{"id":"37692cb499f3d981","repo":"apache/dubbo","slug":"bad-stream-method-signature-method-methodname","errorCode":null,"errorMessage":"Bad stream method signature. method(${methodName}:${paramDesc})","messagePattern":"Bad stream method signature\\. method\\((.+?):(.+?)\\)","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ReflectionMethodDescriptor.java","lineNumber":116,"sourceCode":"        }\n        boolean returnIsVoid = returnClass.getName().equals(void.class.getName());\n        if (returnIsVoid && parameterClasses.length == 1 && isStreamType(parameterClasses[0])) {\n            actualRequestTypes = Collections.emptyList().toArray(new Class<?>[0]);\n            actualResponseType = obtainActualTypeInStreamObserver(\n                    ((ParameterizedType) method.getGenericParameterTypes()[0]).getActualTypeArguments()[0]);\n            return RpcType.SERVER_STREAM;\n        }\n        if (returnIsVoid\n                && parameterClasses.length == 2\n                && !isStreamType(parameterClasses[0])\n                && isStreamType(parameterClasses[1])) {\n            actualRequestTypes = parameterClasses;\n            actualResponseType = obtainActualTypeInStreamObserver(\n                    ((ParameterizedType) method.getGenericParameterTypes()[1]).getActualTypeArguments()[0]);\n            return RpcType.SERVER_STREAM;\n        }\n        if (Arrays.stream(parameterClasses).anyMatch(this::isStreamType) || isStreamType(returnClass)) {\n            throw new IllegalStateException(\n                    \"Bad stream method signature. method(\" + methodName + \":\" + paramDesc + \")\");\n        }\n        // Can not determine client stream because it has same signature with bi_stream\n        return RpcType.UNARY;\n    }\n\n    private boolean isStreamType(Class<?> classType) {\n        return StreamObserver.class.isAssignableFrom(classType);\n    }\n\n    @Override\n    public String getMethodName() {\n        return methodName;\n    }\n\n    @Override\n    public String getJavaMethodName() {\n        return method.getName();","sourceCodeStart":98,"sourceCodeEnd":134,"githubUrl":"https://github.com/apache/dubbo/blob/3a3043227f5571d25eb2889de5bca22f2914843b/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ReflectionMethodDescriptor.java#L98-L134","documentation":"In ReflectionMethodDescriptor.determineRpcType(), Dubbo inspects a method to classify it as UNARY, SERVER_STREAM, or BI_STREAM based on StreamObserver parameters/returns. After the recognized stream patterns are exhausted, if the method still uses a StreamObserver in a way that matches no valid pattern, it is malformed and rejected. This protects the streaming protocol from ambiguous dispatch at export time.","triggerScenarios":"A service interface method uses org.apache.dubbo.common.stream.StreamObserver but its signature does not fit any supported shape: e.g. a non-void return with a single StreamObserver param that is not itself a StreamObserver, three parameters, a StreamObserver as a return type with non-matching params, or raw/non-parameterized StreamObserver usages. Also triggered by >2 parameters where one is a StreamObserver.","commonSituations":"Writing a Dubbo Triple/streaming service by hand and getting the method shape wrong (extra params, wrong return type). Copying a gRPC-style signature without adjusting to Dubbo's StreamObserver contract. Using a raw `StreamObserver` without generic type arguments. Mixing streaming and unary params on the same method.","solutions":["Conform the method to a supported stream signature: BI_STREAM = `StreamObserver<Resp> foo(StreamObserver<Req>)`; SERVER_STREAM = `void foo(StreamObserver<Resp>)` or `void foo(Req, StreamObserver<Resp>)`.","Ensure the return type is exactly void for SERVER_STREAM, or a StreamObserver for BI_STREAM, and that StreamObserver is parameterized (not raw).","Reduce parameters to at most 2 and ensure non-StreamObserver params do not exceed the pattern (0 or 1 request object).","If the method is intended as unary, remove the StreamObserver parameter entirely."],"exampleFix":"// before (throws: 3 params, ambiguous)\nStreamObserver<Resp> foo(Req r, Meta m, StreamObserver<Req> so);\n\n// after (valid BI_STREAM)\nStreamObserver<Resp> foo(StreamObserver<Req> so);\n// or valid SERVER_STREAM\nvoid foo(Req r, StreamObserver<Resp> so);","handlingStrategy":"validation","validationCode":"// Validate a stream method matches a supported shape before export.\nboolean ok =\n    (returnIsVoid && params == 1 && isStreamObserver(params[0])) ||              // SERVER_STREAM\n    (returnIsVoid && params == 2 && !isStreamObserver(params[0]) && isStreamObserver(params[1])) || // SERVER_STREAM\n    (params == 1 && isStreamObserver(params[0]) && isStreamObserver(returnType)); // BI_STREAM\nif (!ok && usesAnyStreamObserver(method)) {\n    throw new IllegalArgumentException(\"Invalid stream signature: \" + method);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep stream methods to the documented shapes (0-1 request param + 1 StreamObserver, void return; or single StreamObserver in and out).","Always parameterize StreamObserver<Req>/<Resp>; avoid raw StreamObserver.","Do not exceed 2 parameters on a streaming method.","Unit-test service interfaces against a signature validator during build."],"tags":["streaming","triple","stream-observer","method-signature","service-interface"],"backgroundTag":null,"analyzedSha":"3a3043227f5571d25eb2889de5bca22f2914843b","analyzedAt":"2026-08-14T00:43:19.853Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}