{"id":"823b0e110f65df96","repo":"apache/kafka","slug":"assignor-supporting-the-cooperative-protocol-viola","errorCode":null,"errorMessage":"Assignor supporting the COOPERATIVE protocol violates its requirements","messagePattern":"Assignor supporting the COOPERATIVE protocol violates its requirements","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"clients/src/main/java/org/apache/kafka/clients/consumer/internals/ConsumerCoordinator.java","lineNumber":749,"sourceCode":"            final Assignment assignment = entry.getValue();\n            final Set<TopicPartition> addedPartitions = new HashSet<>(assignment.partitions());\n            addedPartitions.removeAll(ownedPartitions.get(entry.getKey()));\n            final Set<TopicPartition> revokedPartitions = new HashSet<>(ownedPartitions.get(entry.getKey()));\n            revokedPartitions.removeAll(assignment.partitions());\n\n            totalAddedPartitions.addAll(addedPartitions);\n            totalRevokedPartitions.addAll(revokedPartitions);\n        }\n\n        // if there are overlap between revoked partitions and added partitions, it means some partitions\n        // immediately gets re-assigned to another member while it is still claimed by some member\n        totalAddedPartitions.retainAll(totalRevokedPartitions);\n        if (!totalAddedPartitions.isEmpty()) {\n            log.error(\"With the COOPERATIVE protocol, owned partitions cannot be \" +\n                \"reassigned to other members; however the assignor has reassigned partitions {} which are still owned \" +\n                \"by some members\", totalAddedPartitions);\n\n            throw new IllegalStateException(\"Assignor supporting the COOPERATIVE protocol violates its requirements\");\n        }\n    }\n\n    @Override\n    protected boolean onJoinPrepare(Timer timer, int generation, String memberId) {\n        log.debug(\"Executing onJoinPrepare with generation {} and memberId {}\", generation, memberId);\n        if (joinPrepareTimer == null) {\n            // We should complete onJoinPrepare before rebalanceTimeoutMs,\n            // and continue to join group to avoid member got kicked out from group\n            joinPrepareTimer = time.timer(rebalanceConfig.rebalanceTimeoutMs);\n        } else {\n            joinPrepareTimer.update();\n        }\n\n        // async commit offsets prior to rebalance if auto-commit enabled\n        // and there is no in-flight offset commit request\n        if (autoCommitEnabled && autoCommitOffsetRequestFuture == null) {\n            maybeMarkPartitionsPendingRevocation();","sourceCodeStart":731,"sourceCodeEnd":767,"githubUrl":"https://github.com/apache/kafka/blob/c31c9215e131f8c17e79f8901b48c13ee6aa8e7a/clients/src/main/java/org/apache/kafka/clients/consumer/internals/ConsumerCoordinator.java#L731-L767","documentation":"Thrown as an IllegalStateException from ConsumerCoordinator.validateCooperativeAssignment when a COOPERATIVE-protocol assignor returns an assignment in which a partition still owned by one member is simultaneously assigned to a different member. The cooperative protocol requires a two-step revoke-then-reassign: a partition must first be removed from its current owner's assignment and only in a later rebalance given to a new owner. Reassigning a still-owned partition directly would break the cooperative invariant and cause duplicate consumption / state corruption, so the leader rejects the assignment.","triggerScenarios":"A custom ConsumerPartitionAssignor advertising SupportedProtocol.COOPERATIVE (or the COOPERATIVE protocol is selected and the assignor is not the built-in CooperativeStickyAssignor) returns an assignment where the set of 'added' partitions intersects the set of 'revoked' partitions across members. The leader logs the offending partitions at line 745-747 and throws at line 749 during onLeaderElected.","commonSituations":"Custom cooperative assignor implemented incorrectly (treating cooperative like eager); porting an eager assignor and just tagging it COOPERATIVE without honoring incremental revocation; assignor that ignores each member's ownedPartitions when computing the new assignment; third-party assignor with a cooperative-protocol bug.","solutions":["Use the built-in CooperativeStickyAssignor unless you have a strong reason for a custom one (it is skipped from this validation, see line 697).","In a custom cooperative assignor, always subtract each member's currently owned partitions before reassigning them; revoke first, reassign in the next generation.","Add unit tests asserting that returned added-partitions and revoked-partitions sets are disjoint across all members.","If you cannot fix the assignor immediately, switch the group back to an eager protocol (RangeAssignor) which does not impose this constraint."],"exampleFix":"// before (custom cooperative assignor)\n@Override\npublic GroupAssignment assign(Cluster cluster, GroupSubscription subs) {\n    // ignores each member's ownedPartitions -> may reassign owned partitions\n    return eagerStyleAssignment(cluster, subs);\n}\n\n// after\n@Override\npublic GroupAssignment assign(Cluster cluster, GroupSubscription subs) {\n    // never assign a partition still owned by another member this generation;\n    // leave it unassigned so it is revoked first, then reassign next round.\n    Set<TopicPartition> allOwned = subs.groupSubscription().values().stream()\n        .flatMap(s -> s.ownedPartitions().stream())\n        .collect(Collectors.toSet());\n    Map<String, Assignment> result = new HashMap<>();\n    // ... compute assignment, then drop any partition in allOwned from non-owners\n    return new GroupAssignment(result);\n}","handlingStrategy":"validation","validationCode":"// For any custom ConsumerPartitionAssignor used with COOPERATIVE protocol, add a unit test\n// proving assign() never returns a partition that is still in a member's ownedPartitions\n// in the same generation. If you cannot prove it, switch to the built-in\n// org.apache.kafka.clients.consumer.CooperativeStickyAssignor.","typeGuard":null,"tryCatchPattern":"try {\n    consumer.poll(Duration.ofMillis(1000));\n} catch (IllegalStateException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"COOPERATIVE protocol violates\")) {\n        // custom cooperative assignor is buggy; fall back to CooperativeStickyAssignor\n    } else {\n        throw e;\n    }\n}","preventionTips":["Prefer the built-in CooperativeStickyAssignor over a custom cooperative assignor","If custom, write a regression test asserting assign() never revokes and reassigns the same partition in one generation","Never mix cooperative and eager assignors in the same consumer group","Pin the no-overlap invariant as an automated check in CI"],"tags":["kafka","consumer","rebalance","consumer-group","partition-assignment","cooperative"],"analyzedSha":"c31c9215e131f8c17e79f8901b48c13ee6aa8e7a","analyzedAt":"2026-08-03T12:34:05.770Z","schemaVersion":2}