Repository: apache/kafka
File: clients/src/main/java/org/apache/kafka/clients/consumer/internals/CommitRequestManager.java (Java)

Code before:
                requestTopicDataMap.put(topicPartition.topic(), topic);
            }

            OffsetCommitRequestData data = new OffsetCommitRequestData()
                    .setGroupId(this.groupId)
                    .setGroupInstanceId(groupInstanceId.orElse(null))
                    .setTopics(new ArrayList<>(requestTopicDataMap.values()));
            data = data.setMemberId(memberInfo.memberId);
            if (memberInfo.memberEpoch.isPresent()) {
                data = data.setGenerationIdOrMemberEpoch(memberInfo.memberEpoch.get());
                lastEpochSentOnCommit = memberInfo.memberEpoch;
            } else {
                lastEpochSentOnCommit = Optional.empty();
            }

            OffsetCommitRequest.Builder builder = canUseTopicIds
                    ? OffsetCommitRequest.Builder.forTopicIdsOrNames(data, true)
                    : OffsetCommitRequest.Builder.forTopicNames(data);

            return buildRequestWithResponseHandling(builder);
        }

        /**
         * Handle OffsetCommitResponse. This will complete the request future successfully if no
         * errors are found in the response. If the response contains errors, this will:
         *   - handle expected errors and fail the future with specific exceptions depending on the error
         *   - fail the future with a non-recoverable KafkaException for all unexpected errors (even if retriable)
         */
        @Override
        @SuppressWarnings("NPathComplexity")
        public void onResponse(final ClientResponse response) {
            metricsManager.recordRequestLatency(response.requestLatencyMs());
            long currentTimeMs = response.receivedTimeMs();
            OffsetCommitResponse commitResponse = (OffsetCommitResponse) response.responseBody();
            Set<String> unauthorizedTopics = new HashSet<>();
            boolean failedRequestRegistered = false;
            for (OffsetCommitResponseData.OffsetCommitResponseTopic topic : commitResponse.data().topics()) {
                for (OffsetCommitResponseData.OffsetCommitResponsePartition partition : topic.partitions()) {
                    // Version 10 drop topic name and support to topic id.
                    // We need to find offsetAndMetadata based on topic id and partition index only as
                    // topic name in the response will be emtpy.
                    // For older versions, topic id is zero, and we will find the offsetAndMetadata based on the topic name.
                    TopicPartition tp = (!Uuid.ZERO_UUID.equals(topic.topicId()) && metadata.topicNames().containsKey(topic.topicId())) ?
                        new TopicPartition(metadata.topicNames().get(topic.topicId()), partition.partitionIndex()) :
                        new TopicPartition(topic.name(), partition.partitionIndex());

                    Errors error = Errors.forCode(partition.errorCode());
                    if (error == Errors.NONE) {
                        OffsetAndMetadata offsetAndMetadata = offsets.get(tp);
                        if (offsetAndMetadata == null) {
                            throw new IllegalStateException("Can't find metadata for topic id " + topic.topicId() +

Review comment (bug):
If we don't have topic IDs in hand when committing offsets, I'm afraid we cannot simply move on and generate a request `forTopicNames` using the last stable version. As of v10 (not stable yet but will be eventually), the `OffsetCommit` API supports topics ID exclusively, so building with names like this would fail, right? 

https://github.com/apache/kafka/blob/92d9dc19b22529895c430bf2551b551cbfbcaad1/clients/src/main/java/org/apache/kafka/common/requests/OffsetCommitRequest.java#L68-L74
 
This relates to the comment above: if we don't have topic IDs, we should request metadata, but also we cannot continue to generate requests really. So, should we just request metadata and return? not generating any request on that round, but they will be generated on the next iteration of the background thread after the metadata is resolved. Thoughts?

Code after:
                    );

                topic.partitions().add(new OffsetCommitRequestData.OffsetCommitRequestPartition()
                    .setPartitionIndex(topicPartition.partition())
                    .setCommittedOffset(offsetAndMetadata.offset())
                    .setCommittedLeaderEpoch(offsetAndMetadata.leaderEpoch().orElse(RecordBatch.NO_PARTITION_LEADER_EPOCH))
                    .setCommittedMetadata(offsetAndMetadata.metadata())
                );
                requestTopicDataMap.put(topicPartition.topic(), topic);
            }

            OffsetCommitRequestData data = new OffsetCommitRequestData()
                    .setGroupId(this.groupId)
                    .setGroupInstanceId(groupInstanceId.orElse(null))
                    .setTopics(new ArrayList<>(requestTopicDataMap.values()));
            data = data.setMemberId(memberInfo.memberId);
            if (memberInfo.memberEpoch.isPresent()) {
                data = data.setGenerationIdOrMemberEpoch(memberInfo.memberEpoch.get());
                lastEpochSentOnCommit = memberInfo.memberEpoch;
            } else {
                lastEpochSentOnCommit = Optional.empty();
            }

            OffsetCommitRequest.Builder builder = canUseTopicIds
                    ? OffsetCommitRequest.Builder.forTopicIdsOrNames(data, true)
                    : OffsetCommitRequest.Builder.forTopicNames(data);

            return buildRequestWithResponseHandling(builder);
        }

        /**
         * Handle OffsetCommitResponse. This will complete the request future successfully if no
         * errors are found in the response. If the response contains errors, this will:
         *   - handle expected errors and fail the future with specific exceptions depending on the error
         *   - fail the future with a non-recoverable KafkaException for all unexpected errors (even if retriable)
         */
        @Override
        @SuppressWarnings("NPathComplexity")
        public void onResponse(final ClientResponse response) {
            metricsManager.recordRequestLatency(response.requestLatencyMs());
            long currentTimeMs = response.receivedTimeMs();
            OffsetCommitResponse commitResponse = (OffsetCommitResponse) response.responseBody();
            Set<String> unauthorizedTopics = new HashSet<>();
            boolean failedRequestRegistered = false;
            for (OffsetCommitResponseData.OffsetCommitResponseTopic topic : commitResponse.data().topics()) {
                for (OffsetCommitResponseData.OffsetCommitResponsePartition partition : topic.partitions()) {
                    // Version 10 drops topic name, and supports topic id.
                    // We need to find offsetAndMetadata based on topic id and partition index only as
                    // topic name in the response will be emtpy.
                    // For older versions, topic id is zero, and we will find the offsetAndMetadata based on the topic name.
                    TopicPartition tp = (!Uuid.ZERO_UUID.equals(topic.topicId()) && metadata.topicNames().containsKey(topic.topicId())) ?