apache/kafka · error · IllegalArgumentException

Invalid producer ID and epoch values: {producerId}:{epoch}.

Error message

Invalid producer ID and epoch values: {producerId}:{epoch}. Both must be >= 0

What it means

Thrown by the PreparedTxnState(String) constructor when deserializing a two-phase-commit transaction state string of the form 'producerId:epoch' where either the parsed producerId or epoch is negative. Kafka reserves negative sentinels (NO_PRODUCER_ID=-1, NO_PRODUCER_EPOCH=-1) to mean 'uninitialized', and a real prepared transaction must always carry non-negative id and epoch, so a negative value signals corrupt or hand-edited state.

Source

Thrown at clients/src/main/java/org/apache/kafka/clients/producer/PreparedTxnState.java:63

    public PreparedTxnState(String serializedState) {
        if (serializedState == null || serializedState.isEmpty()) {
            this.producerId = RecordBatch.NO_PRODUCER_ID;
            this.epoch = RecordBatch.NO_PRODUCER_EPOCH;
            return;
        }

        try {
            String[] parts = serializedState.split(":");
            if (parts.length != 2) {
                throw new IllegalArgumentException("Invalid serialized transaction state format: " + serializedState);
            }

            this.producerId = Long.parseLong(parts[0]);
            this.epoch = Short.parseShort(parts[1]);

            // Validate the producerId and epoch values.
            if (!(this.producerId >= 0 && this.epoch >= 0)) {
                throw new IllegalArgumentException("Invalid producer ID and epoch values: " +
                    producerId + ":" + epoch + ". Both must be >= 0");
            }
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("Invalid serialized transaction state format: " + serializedState, e);
        }
    }

    /**
     * Creates a new PreparedTxnState with the given producer ID and epoch
     *
     * @param producerId        The producer ID
     * @param epoch             The producer epoch
     */
    PreparedTxnState(long producerId, short epoch) {
        this.producerId = producerId;
        this.epoch = epoch;
    }

View on GitHub (pinned to c31c9215e1)

Solutions

  1. Inspect the serializedState string being passed in and confirm it came from PreparedTxnState.toString() of an initialized transaction (non-empty, 'id:epoch' with both >=0).
  2. Treat empty/null strings as 'no transaction' by passing them through unchanged instead of substituting negative sentinel values.
  3. Regenerate the state from the transactional coordinator / InitProducerId rather than restoring a tampered checkpoint.

Example fix

// before
new PreparedTxnState("-1:-1")

// after
new PreparedTxnState("")  // or omit / pass null to mean 'uninitialized'
Defensive patterns

Strategy: validation

Validate before calling

// Validate a persisted 'producerId:epoch' string BEFORE constructing PreparedTxnState.
static boolean isValidPreparedTxnState(String s) {
    if (s == null || s.isEmpty()) return true; // empty == uninitialized, always legal
    String[] parts = s.split(":", -1);
    if (parts.length != 2) return false;
    try {
        long pid = Long.parseLong(parts[0]);
        short epoch = Short.parseShort(parts[1]);
        return pid >= 0 && epoch >= 0; // both must be non-negative
    } catch (NumberFormatException e) {
        return false;
    }
}
// Caller:
if (!isValidPreparedTxnState(stored)) { /* re-initialize txn instead of constructing */ }

Try / catch

// PreparedTxnState(String) throws IllegalArgumentException on bad input.
try {
    PreparedTxnState state = new PreparedTxnState(stored);
} catch (IllegalArgumentException e) {
    // Treat as no transaction: fall back to a fresh empty state.
    log.warn("Discarding corrupt prepared txn state '{}'", stored, e);
    state = new PreparedTxnState();
}

Prevention

When it happens

Trigger: Constructing `new PreparedTxnState("-1:5")`, `new PreparedTxnState("10:-3")`, or any string where the first or second ':'-separated token parses to a negative long/short. The check at PreparedTxnState.java:62 is reached only after parts.length==2 and successful Long.parseLong/Short.parseShort.

Common situations: Persisting the toString() of an uninitialized PreparedTxnState that was manually rewritten, replaying a checkpoint/offset store whose producer state got corrupted, or migrating state files between incompatible Kafka versions where the sentinel conventions differ.

Related errors


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