{"record":{"id":"59e63a4e6d74cdd7","repo":"nathanmarz/storm","slug":"output-stream-of-last-component-in-lineardrpctopology-must","errorCode":null,"errorMessage":"Output stream of last component in LinearDRPCTopology must contain exactly two fields. The first should be the request id, and the second should be the result.","messagePattern":"Output stream of last component in LinearDRPCTopology must contain exactly two fields\\. The first should be the request id, and the second should be the result\\.","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"storm-core/src/jvm/backtype/storm/drpc/LinearDRPCTopologyBuilder.java","lineNumber":157,"sourceCode":"                    declaration.declare(prevId, declarer);\n                }\n            }\n            if(i>0) {\n                declarer.directGrouping(boltId(i-1), Constants.COORDINATED_STREAM_ID); \n            }\n        }\n        \n        IRichBolt lastBolt = _components.get(_components.size()-1).bolt;\n        OutputFieldsGetter getter = new OutputFieldsGetter();\n        lastBolt.declareOutputFields(getter);\n        Map<String, StreamInfo> streams = getter.getFieldsDeclaration();\n        if(streams.size()!=1) {\n            throw new RuntimeException(\"Must declare exactly one stream from last bolt in LinearDRPCTopology\");\n        }\n        String outputStream = streams.keySet().iterator().next();\n        List<String> fields = streams.get(outputStream).get_output_fields();\n        if(fields.size()!=2) {\n            throw new RuntimeException(\"Output stream of last component in LinearDRPCTopology must contain exactly two fields. The first should be the request id, and the second should be the result.\");\n        }\n\n        builder.setBolt(boltId(i), new JoinResult(PREPARE_ID))\n                .fieldsGrouping(boltId(i-1), outputStream, new Fields(fields.get(0)))\n                .fieldsGrouping(PREPARE_ID, PrepareRequest.RETURN_STREAM, new Fields(\"request\"));\n        i++;\n        builder.setBolt(boltId(i), new ReturnResults())\n                .noneGrouping(boltId(i-1));\n        return builder.createTopology();\n    }\n    \n    private static String boltId(int index) {\n        return \"bolt\" + index;\n    }\n    \n    private static class Component {\n        public IRichBolt bolt;\n        public int parallelism;","sourceCodeStart":139,"sourceCodeEnd":175,"githubUrl":"https://github.com/nathanmarz/storm/blob/cdb116e942666973bc4eaa0df098d5bab82739e7/storm-core/src/jvm/backtype/storm/drpc/LinearDRPCTopologyBuilder.java#L139-L175","documentation":"After requiring exactly one stream, createTopology checks that the last bolt's output stream has exactly two fields: the request id followed by the result, since JoinResult and the return-logic rely on that contract. Otherwise a RuntimeException is thrown.","triggerScenarios":"Declaring the last bolt's single stream with a field count != 2 (e.g. Fields(\"id\",\"result\",\"extra\") or Fields(\"id\")) in a LinearDRPCTopology.","commonSituations":"Adding extra diagnostic fields to the final emit; returning tuples/lists and forgetting the count contract; copying a normal (non-DRPC) bolt as the last component.","solutions":["Emit exactly two values per tuple: the request id, then the result (emit(new Object[]{id, result})).","Declare fields as new Fields(\"id\", \"result\") — names are yours, count must be 2.","Move extra data into the result object itself (e.g. a Map or POJO holding all results)."],"exampleFix":"// before\ndeclarer.declare(new Fields(\"id\", \"result\", \"latency\"));\n// after\ndeclarer.declare(new Fields(\"id\", \"result\")); // put latency inside result object","handlingStrategy":"validation","validationCode":"OutputFieldsGetter g = new OutputFieldsGetter();\nlastBolt.declareOutputFields(g);\nMap<String, StreamInfo> streams = g.getFieldsDeclaration();\nList<String> fields = streams.values().iterator().next().get_output_fields();\nif (fields.size() != 2) {\n    throw new IllegalStateException(\"Last DRPC bolt must output exactly 2 fields (id, result), got \" + fields.size());\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always emit(new Object[]{requestId, result}) from the last bolt — exactly two values.","Bundle extra outputs into the result object instead of extra tuple fields.","Write a topology-structure test validating the two-field contract."],"tags":["drpc","topology-definition","tuple-fields","storm"],"backgroundTag":"schema-validation-failed","analyzedSha":"cdb116e942666973bc4eaa0df098d5bab82739e7","analyzedAt":"2026-09-12T14:30:00.714Z","contentChangedAt":"2026-09-12T14:30:00.714Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}