apache/kafka · error · IllegalArgumentException

Cannot specify a negative version level.

Error message

Cannot specify a negative version level.

What it means

Thrown by the FeatureUpdate constructor when maxVersionLevel is negative. Version levels are non-negative shorts where 0 is the delete sentinel and positive values are real feature levels; a negative value is never valid and indicates a bug or arithmetic underflow in the caller. The check runs after the maxVersionLevel==0 UPGRADE guard so it catches any value < 0.

Source

Thrown at clients/src/main/java/org/apache/kafka/clients/admin/FeatureUpdate.java:78

    /**
     * @param maxVersionLevel   The new maximum version level for the finalized feature.
     *                          a value of zero is special and indicates that the update is intended to
     *                          delete the finalized feature, and should be accompanied by setting
     *                          the upgradeType to safe or unsafe.
     * @param upgradeType     Indicate what kind of upgrade should be performed in this operation.
     *                          - UPGRADE: upgrading the feature level
     *                          - SAFE_DOWNGRADE: only downgrades which do not result in metadata loss are permitted
     *                          - UNSAFE_DOWNGRADE: any downgrade, including those which may result in metadata loss, are permitted
     */
    public FeatureUpdate(final short maxVersionLevel, final UpgradeType upgradeType) {
        if (maxVersionLevel == 0 && upgradeType.equals(UpgradeType.UPGRADE)) {
            throw new IllegalArgumentException(String.format(
                    "The upgradeType flag should be set to SAFE_DOWNGRADE or UNSAFE_DOWNGRADE when the provided maxVersionLevel:%d is < 1.",
                    maxVersionLevel));
        }
        if (maxVersionLevel < 0) {
            throw new IllegalArgumentException("Cannot specify a negative version level.");
        }
        this.maxVersionLevel = maxVersionLevel;
        this.upgradeType = upgradeType;
    }

    public short maxVersionLevel() {
        return maxVersionLevel;
    }

    public UpgradeType upgradeType() {
        return upgradeType;
    }

    @Override
    public boolean equals(Object other) {
        if (this == other) {
            return true;
        }

View on GitHub (pinned to c31c9215e1)

Solutions

  1. Clamp computed level values to >= 0 before constructing the FeatureUpdate.
  2. Validate input from external sources (config, RPC, CLI args) is non-negative before passing it in.
  3. Use 0 explicitly when you intend deletion (with the appropriate downgrade type).
  4. Add a unit test asserting FeatureUpdate rejects negative levels to catch regressions.

Example fix

// before
short target = (short) (currentLevel - offset); // can be negative
new FeatureUpdate(target, FeatureUpdate.UpgradeType.UPGRADE);

// after
short target = (short) Math.max(0, currentLevel - offset);
FeatureUpdate.UpgradeType type = target == 0
    ? FeatureUpdate.UpgradeType.SAFE_DOWNGRADE
    : FeatureUpdate.UpgradeType.UPGRADE;
new FeatureUpdate(target, type);
Defensive patterns

Strategy: validation

Validate before calling

// maxVersionLevel must be >= 0. Reject negatives before constructing.
short maxVersionLevel = ...;
if (maxVersionLevel < 0) {
    throw new IllegalArgumentException(
        "maxVersionLevel must be >= 0, got " + maxVersionLevel);
}
FeatureUpdate update = new FeatureUpdate(maxVersionLevel, upgradeType);

Type guard

// Guard: only non-negative shorts are valid FeatureUpdate levels.
static boolean isNonNegativeLevel(short level) {
    return level >= 0;
}

Try / catch

try {
    FeatureUpdate update = new FeatureUpdate(maxVersionLevel, upgradeType);
} catch (IllegalArgumentException e) {
    // Negative level reached the constructor. Clamp or reject upstream.
    log.error("Negative FeatureUpdate level {}: {}", maxVersionLevel, e.getMessage());
}

Prevention

When it happens

Trigger: Constructing new FeatureUpdate((short) -1, anyType), or passing a short computed from a subtraction, cast, or external input that went negative. Reached from admin clients, tooling, or deserialization paths that build a FeatureUpdate from untrusted numbers.

Common situations: Caller computes (currentLevel - 1) without clamping; short underflow when reading raw bytes; malformed external config feeding a negative value; test fixtures with wrong literals; client parsing a corrupted controller response.

Related errors


AI-assisted analysis of apache/kafka@c31c9215e1 (2026-08-03). Data as JSON: /data/errors/c12e77d178f21f5c.json. Report an issue: GitHub.