apache/cassandra · error · IllegalArgumentException

No group found for range of supplied replica

Error message

No group found for range of supplied replica %s (%s)

What it means

ReplicaGroups.Builder.withoutReplica removes a single replica from the placement group owning its range. It throws IllegalArgumentException when no group in the current replicaGroups map matches the replica's exact range, i.e. the caller asked to remove a replica from a range this builder does not track.

Solutions

  1. Re-read the current placements from ClusterMetadata and rebuild the Builder so the replica's range matches an existing group
  2. Confirm the operation is idempotent/retryable: if the range group is already gone the removal may have been applied; skip instead of throwing
  3. Ensure the replica object passed to withoutReplica comes from the same snapshot the Builder was created from (same epoch)
  4. Re-run the placement transformation from a fresh metadata read

Example fix

// before
builder.withoutReplica(epoch, replica);
// after
VersionedEndpoints.ForRange group = replicaGroups.get(replica.range());
if (group != null)
    builder.withoutReplica(epoch, replica);
Defensive patterns

Strategy: try-catch

Validate before calling

VersionedEndpoints.ForRange group = builderReplicaGroups.get(replica.range());
if (group == null || !group.get().contains(replica.endpoint()))
    throw new IllegalArgumentException("builder lacks replica: " + replica);

Try / catch

try { builder.withoutReplica(epoch, replica); } catch (IllegalArgumentException e) { /* skip: removal already applied or rebuild from fresh metadata */ }

Prevention

When it happens

Trigger: Calling withoutReplica(epoch, replica) with a replica whose Range<Token> is not a key in the builder's replicaGroups map — typically when computing a placement delta (e.g. during decommission or range movement) using a replica taken from a different/stale placements snapshot.

Common situations: A decommission or move operation was partially applied so the range in the builder differs from the replica's range; passing replicas from a canonical placement into a builder seeded from the pending placement; concurrent metadata transformations changed the ranges between reads.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


AI-assisted analysis of apache/cassandra@88fd0f6a0e (2026-09-10). Data as JSON: /api/errors/6ec5656b6dcb65ad. Report an issue: GitHub.

Appendix: source

Thrown at src/java/org/apache/cassandra/tcm/ownership/ReplicaGroups.java:414

                                                   EndpointsForRange old = v.get();
                                                   return VersionedEndpoints.forRange(epoch,
                                                                                      old.newBuilder(old.size() + 1)
                                                                                         .addAll(old)
                                                                                         .add(replica, ReplicaCollection.Builder.Conflict.NONE)
                                                                                         .build());
                                               });
;            if (group == null)
                replicaGroups.put(replica.range(), VersionedEndpoints.forRange(epoch, EndpointsForRange.of(replica)));
            return this;

        }

        public Builder withoutReplica(Epoch epoch, Replica replica)
        {
            Range<Token> range = replica.range();
            VersionedEndpoints.ForRange group = replicaGroups.get(range);
            if (group == null)
                throw new IllegalArgumentException(String.format("No group found for range of supplied replica %s (%s)",
                                                                 replica, range));
            EndpointsForRange without = group.get().without(Collections.singleton(replica.endpoint()));
            if (without.isEmpty())
                replicaGroups.remove(range);
            else
                replicaGroups.put(range, VersionedEndpoints.forRange(epoch, without));
            return this;
        }

        public Builder withReplicaGroup(VersionedEndpoints.ForRange replicas)
        {
            VersionedEndpoints.ForRange group =
                replicaGroups.computeIfPresent(replicas.range(),
                                               (t, v) -> {
                                                   EndpointsForRange old = v.get();
                                                   return VersionedEndpoints.forRange(Epoch.max(v.lastModified(), replicas.lastModified()),
                                                                                      combine(old, replicas.get()));
                                               });

View on GitHub (pinned to 88fd0f6a0e)