provectus/kafka-ui · error · ValidationException

Something went wrong during removing replicas

Error message

Something went wrong during removing replicas

What it means

The mirror of error 38: when decreasing a topic's replication factor, getPartitionsReassignments iterates each partition's current assignment and drops replica entries until only the requested number remain. If the removal loop cannot reduce the broker set to totalReplicationFactor (assignment structure shorter than expected or constrained), it throws this ValidationException rather than emitting an invalid reassignment. The adjacent else-branch also shows RF already equal to the request is rejected separately.

Solutions

  1. Verify the requested totalReplicationFactor is strictly less than the topic's current replication factor and >= 1.
  2. Inspect the topic's current assignments (kafka-topics --describe) for unusual layouts; normalize them first or reassign manually with kafka-reassign-partitions.
  3. If Kafka version supports it, note that decreasing RF via reassignment may be restricted — remove replicas manually via a reassignment JSON.
  4. Ensure the topic isn't in the middle of another reassignment that altered assignments unexpectedly.

Example fix

// before: reducing RF to 2 while a partition assignment is malformed/unexpected
POST /api/clusters/local/topics/orders/replication-factor
{ "totalReplicationFactor": 2 }  // fails building reassignment
// after: normalize assignments via manual reassignment first
{"version":1,"partitions":[{"topic":"orders","partition":0,"replicas":[1,2,3]}]}
// then request RF decrease
Defensive patterns

Strategy: validation

Validate before calling

const topic = await fetch(`/api/clusters/${cluster}/topics/${name}`).then(r => r.json());
const currentRF = Math.max(...topic.partitionReplications || [1]);
if (targetRF >= currentRF) {
  throw new Error(`Target RF ${targetRF} must be lower than current RF ${currentRF}`);
}

Try / catch

try {
  await changeReplicationFactor(cluster, topic, { totalReplicationFactor: rf });
} catch (e) {
  if (e.message.includes('removing replicas') || e.message.includes('already equals')) {
    // reassign partitions manually or skip — RF change not applicable
  } else { throw e; }
}

Prevention

When it happens

Trigger: Calling changeReplicationFactor (POST /api/clusters/{cluster}/topics/{name}/replication-factor) with totalReplicationFactor lower than the current RF while the algorithm cannot shrink the current assignments down to the target count — e.g., current assignment lists that don't contain enough removable entries under the constraints applied while selecting brokers to keep.

Common situations: Reducing RF on a topic whose partitions were previously manually reassigned with custom replica layouts; target RF equals or exceeds some partitions' current assignable broker set; requesting the same RF as current (which instead yields 'Replication factor already equals requested'); conflating replication-factor change with partition-count change.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of provectus/kafka-ui@83b5a60cc0 (2026-09-08). Data as JSON: /api/errors/eb756a50ef4b0b48. Report an issue: GitHub.

Appendix: source

Thrown at kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/TopicsService.java:345

            .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
            .map(Map.Entry::getKey)
            .collect(toList());

        // Iterate brokers and try to remove them from assignment
        // while partition replicas count != requested replication factor
        for (Integer broker : brokersUsageList) {
          // Check is the broker the leader of partition
          if (!topic.getPartitions().get(partition).getLeader()
              .equals(broker)) {
            brokers.remove(broker);
            brokersUsage.merge(broker, -1, Integer::sum);
          }
          if (brokers.size() == replicationFactorChange.getTotalReplicationFactor()) {
            break;
          }
        }
        if (brokers.size() != replicationFactorChange.getTotalReplicationFactor()) {
          throw new ValidationException("Something went wrong during removing replicas");
        }
      }
    } else {
      throw new ValidationException("Replication factor already equals requested");
    }

    // Return result map
    return currentAssignment.entrySet().stream().collect(toMap(
        e -> new TopicPartition(topic.getName(), e.getKey()),
        e -> Optional.of(new NewPartitionReassignment(e.getValue()))
    ));
  }

  private Map<Integer, List<Integer>> getCurrentAssignment(InternalTopic topic) {
    return topic.getPartitions().values().stream()
        .collect(toMap(
            InternalPartition::getPartition,
            p -> p.getReplicas().stream()

View on GitHub (pinned to 83b5a60cc0)