{"id":"85f84623b101b836","repo":"apache/kafka","slug":"offsets-from-multiple-consumer-groups-were-request","errorCode":null,"errorMessage":"Offsets from multiple consumer groups were requested. Use partitionsToOffsetAndMetadata(groupId) instead to get future for a specific group.","messagePattern":"Offsets from multiple consumer groups were requested\\. Use partitionsToOffsetAndMetadata\\(groupId\\) instead to get future for a specific group\\.","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"clients/src/main/java/org/apache/kafka/clients/admin/ListConsumerGroupOffsetsResult.java","lineNumber":53,"sourceCode":" * <p>\n */\n@InterfaceAudience.Public\npublic class ListConsumerGroupOffsetsResult {\n\n    final Map<String, KafkaFuture<Map<TopicPartition, OffsetAndMetadata>>> futures;\n\n    ListConsumerGroupOffsetsResult(final Map<CoordinatorKey, KafkaFuture<Map<TopicPartition, OffsetAndMetadata>>> futures) {\n        this.futures = futures.entrySet().stream()\n                .collect(Collectors.toMap(e -> e.getKey().idValue, Entry::getValue));\n    }\n\n    /**\n     * Return a future which yields a map of topic partitions to OffsetAndMetadata objects.\n     * If the group does not have a committed offset for this partition, the corresponding value in the returned map will be null.\n     */\n    public KafkaFuture<Map<TopicPartition, OffsetAndMetadata>> partitionsToOffsetAndMetadata() {\n        if (futures.size() != 1) {\n            throw new IllegalStateException(\"Offsets from multiple consumer groups were requested. \" +\n                    \"Use partitionsToOffsetAndMetadata(groupId) instead to get future for a specific group.\");\n        }\n        return futures.values().iterator().next();\n    }\n\n    /**\n     * Return a future which yields a map of topic partitions to OffsetAndMetadata objects for\n     * the specified group. If the group doesn't have a committed offset for a specific\n     * partition, the corresponding value in the returned map will be null.\n     */\n    public KafkaFuture<Map<TopicPartition, OffsetAndMetadata>> partitionsToOffsetAndMetadata(String groupId) {\n        if (!futures.containsKey(groupId))\n            throw new IllegalArgumentException(\"Offsets for consumer group '\" + groupId + \"' were not requested.\");\n        return futures.get(groupId);\n    }\n\n    /**\n     * Return a future which yields all {@code Map<String, Map<TopicPartition, OffsetAndMetadata>} objects,","sourceCodeStart":35,"sourceCodeEnd":71,"githubUrl":"https://github.com/apache/kafka/blob/c31c9215e131f8c17e79f8901b48c13ee6aa8e7a/clients/src/main/java/org/apache/kafka/clients/admin/ListConsumerGroupOffsetsResult.java#L35-L71","documentation":"Thrown by ListConsumerGroupOffsetsResult.partitionsToOffsetAndMetadata() (the no-arg overload) when the underlying result carries futures for more than one consumer group. The no-arg accessor is only valid when exactly one group was queried; when listConsumerGroupOffsets was called with a Map of multiple group keys, you must disambiguate by passing the specific groupId to the partitionsToOffsetAndMetadata(String) overload (or call all() for the full multi-group result).","triggerScenarios":"Calling admin.listConsumerGroupOffsets(Map.of(groupA, partitions, groupB, partitions)) and then invoking .partitionsToOffsetAndMetadata() on the result without a groupId; the futures map has size 2, so the no-arg accessor throws IllegalStateException.","commonSituations":"Refactoring single-group offset tooling to a multi-group batch query without switching the accessor overload; generic helper that always calls the no-arg partitionsToOffsetAndMetadata regardless of how many groups were requested; copying example code that assumed a single group.","solutions":["Call partitionsToOffsetAndMetadata(groupId) with the specific group you want from the multi-group result.","Or call all() to obtain a future yielding Map<String, Map<TopicPartition, OffsetAndMetadata>> for every requested group.","If the intent truly is a single group, call listConsumerGroupOffsets(singleGroupId) (or a one-entry map) so the no-arg accessor remains valid."],"exampleFix":"// before\nListConsumerGroupOffsetsResult result =\n    admin.listConsumerGroupOffsets(Map.of(\"g1\", Set.of(tp), \"g2\", Set.of(tp)));\nMap<TopicPartition, OffsetAndMetadata> offsets =\n    result.partitionsToOffsetAndMetadata().get(); // IllegalStateException\n\n// after - pick a specific group\nMap<TopicPartition, OffsetAndMetadata> g1Offsets =\n    result.partitionsToOffsetAndMetadata(\"g1\").get();\n// or get every group at once\nMap<String, Map<TopicPartition, OffsetAndMetadata>> all =\n    result.all().get();","handlingStrategy":"validation","validationCode":"ListConsumerGroupOffsetsResult result = admin.listConsumerGroupOffsets(requestedGroups);\nif (requestedGroups.size() != 1) {\n    // multi-group: use all() or partitionsToOffsetAndMetadata(groupId) per group\n    Map<String, Map<TopicPartition, OffsetAndMetadata>> all = result.all().get();\n} else {\n    Map<TopicPartition, OffsetAndMetadata> single =\n        result.partitionsToOffsetAndMetadata(requestedGroups.iterator().next()).get();\n}","typeGuard":null,"tryCatchPattern":"if (result.futures().size() == 1) {\n    result.partitionsToOffsetAndMetadata(); // safe\n} else {\n    // multiple groups requested -> call partitionsToOffsetAndMetadata(groupId) or all()\n    result.all().get();\n}","preventionTips":["The no-arg partitionsToOffsetAndMetadata() requires exactly one group; always prefer the partitionsToOffsetAndMetadata(groupId) overload unless you control the request size.","Track how many groups you passed to listConsumerGroupOffsets and branch on size before consuming the result.","For batch listings, use result.all() instead of assuming a single-group shape."],"tags":["admin-client","consumer-groups","offsets","argument-validation","api-contract"],"analyzedSha":"c31c9215e131f8c17e79f8901b48c13ee6aa8e7a","analyzedAt":"2026-08-03T12:34:05.770Z","schemaVersion":2}