{"id":"ca546d365ae86ab9","repo":"apache/kafka","slug":"you-can-only-check-the-position-for-partitions-ass","errorCode":null,"errorMessage":"You can only check the position for partitions assigned to this consumer.","messagePattern":"You can only check the position for partitions assigned to this consumer\\.","errorType":"exception","errorClass":"java.lang.IllegalStateException","httpStatus":null,"severity":"error","filePath":"clients/src/main/java/org/apache/kafka/clients/consumer/internals/AsyncKafkaConsumer.java","lineNumber":1236,"sourceCode":"                offsetResetStrategy,\n                defaultApiTimeoutDeadlineMs())\n            );\n        } finally {\n            release();\n        }\n    }\n\n    @Override\n    public long position(TopicPartition partition) {\n        return position(partition, defaultApiTimeoutMs);\n    }\n\n    @Override\n    public long position(TopicPartition partition, Duration timeout) {\n        acquireAndEnsureOpen();\n        try {\n            if (!subscriptions.isAssigned(partition))\n                throw new IllegalStateException(\"You can only check the position for partitions assigned to this consumer.\");\n\n            Timer timer = time.timer(timeout);\n            do {\n                SubscriptionState.FetchPosition position = subscriptions.validPosition(partition);\n                if (position != null)\n                    return position.offset;\n\n                updateFetchPositions(timer);\n                timer.update();\n                wakeupTrigger.maybeTriggerWakeup();\n            } while (timer.notExpired());\n\n            throw new TimeoutException(\"Timeout of \" + timeout.toMillis() + \"ms expired before the position \" +\n                \"for partition \" + partition + \" could be determined\");\n        } finally {\n            release();\n        }\n    }","sourceCodeStart":1218,"sourceCodeEnd":1254,"githubUrl":"https://github.com/apache/kafka/blob/c31c9215e131f8c17e79f8901b48c13ee6aa8e7a/clients/src/main/java/org/apache/kafka/clients/consumer/internals/AsyncKafkaConsumer.java#L1218-L1254","documentation":"IllegalStateException thrown by AsyncKafkaConsumer.position(TopicPartition, Duration) when the requested partition is not in the consumer's current assignment (subscriptions.isAssigned(partition) is false). position requires an active fetch position, which only exists for assigned partitions; querying a partition you did not subscribe/assign yields no meaningful value. The check runs inside the acquired consumer lock before any retry/timeout loop.","triggerScenarios":"Calling consumer.position(tp) for a TopicPartition that is not part of the consumer's current assignment. Commonly happens when code computes partitions from topic metadata rather than from consumer.assignment(), or when checking position for a partition whose assignment was lost after a rebalance.","commonSituations":"Calling position on a partition the user assumed was assigned but was reassigned to another consumer in the group after a rebalance; mixing partition sets across multiple consumer instances; using AdminClient-described partitions to call position instead of consumer.assignment(); race between a rebalance callback revoking partitions and a position() call on the same thread.","solutions":["Only call position for partitions returned by consumer.assignment() (or within an onPartitionsAssigned callback).","Refresh the assignment after a rebalance before querying positions; cache nothing across rebalances.","If the partition is genuinely not assigned, do not call position — reassign or rebalance first."],"exampleFix":"// before\nTopicPartition tp = new TopicPartition(\"orders\", 5); // not necessarily assigned\nlong pos = consumer.position(tp);\n\n// after\nTopicPartition tp = new TopicPartition(\"orders\", 5);\nif (consumer.assignment().contains(tp)) {\n    long pos = consumer.position(tp);\n} else {\n    // reassign or skip\n}","handlingStrategy":"validation","validationCode":"// position(TopicPartition) requires the partition to be currently assigned.\n// Check assignment immediately before the call — assignments change on rebalance.\nstatic long safePosition(Consumer<?, ?> c, TopicPartition tp, Duration timeout) {\n    Set<TopicPartition> assigned = c.assignment();\n    if (!assigned.contains(tp)) {\n        throw new IllegalStateException(\n            \"Cannot query position for \" + tp + \"; it is not among the currently assigned \" +\n            \"partitions: \" + assigned);\n    }\n    return c.position(tp, timeout);\n}\n\n// Usage:\n//   long off = safePosition(consumer, tp, Duration.ofSeconds(10));\n//\n// Note: assignment() reflects the latest rebalance; calling it right before position()\n// minimizes the (non-zero) window in which a revocation could still race you.","typeGuard":"// Brand a TopicPartition as 'Assigned' so unassigned partitions cannot be passed\n// to position() by construction.\npublic static Set<TopicPartition> assignedSet(Consumer<?, ?> c) {\n    return c.assignment(); // already an immutable snapshot\n}\n// Then accept only partitions proven to be in that set:\nstatic long positionOf(Consumer<?, ?> c, TopicPartition tp, Duration timeout) {\n    if (!c.assignment().contains(tp))\n        throw new IllegalStateException(tp + \" not assigned\");\n    return c.position(tp, timeout);\n}\n//\n// TypeScript analogue:\n//   type Assigned = TopicPartition & { __brand: 'Assigned' };\n//   function assigned(c: Consumer): Assigned[] { return c.assignment() as Assigned[]; }\n//   function position(c: Consumer, tp: Assigned): number { return c.position(tp); }","tryCatchPattern":"// IllegalStateException from position() means the partition isn't assigned right now;\n// the correct response is to refresh assignment, not to retry blindly.\ntry {\n    long off = consumer.position(tp, Duration.ofSeconds(10));\n} catch (IllegalStateException e) {\n    if (e.getMessage().contains(\"assigned to this consumer\")) {\n        log.info(\"{} not currently assigned; refreshing assignment snapshot\", tp);\n        Set<TopicPartition> current = consumer.assignment();\n        // re-evaluate: skip this partition, or wait for the next poll() to trigger rebalance\n        continue; // in a per-partition loop\n    }\n    throw e;\n}","preventionTips":["Only query positions for partitions you obtained from the most recent consumer.assignment() snapshot, not from a cached or external list.","Inside a ConsumerRebalanceListener, do not call position() during onPartitionsRevoked — the set is shrinking; query in onPartitionsAssigned instead.","Re-check assignment on every loop iteration if your poll cycle is long; rebalances can revoke partitions mid-loop.","In tests, always subscribe/assign and call poll() at least once before asserting on position() — assignment is not established until the coordinator communicates."],"tags":["consumer","position","assignment","rebalance"],"analyzedSha":"c31c9215e131f8c17e79f8901b48c13ee6aa8e7a","analyzedAt":"2026-08-03T12:34:05.770Z","schemaVersion":2}