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
- 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).
- Treat empty/null strings as 'no transaction' by passing them through unchanged instead of substituting negative sentinel values.
- 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
- Only persist the value returned by PreparedTxnState.toString(); never hand-format 'producerId:epoch'.
- Treat external/persisted transaction state as untrusted — validate or wrap construction in try/catch.
- If you load state from disk/DB, prefer storing producerId and epoch as separate typed columns rather than a delimited string.
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
- Cannot set transaction.timeout.ms when transaction.two.phase
- Invalid serialized transaction state format: {serializedStat
- Must set retries to non-zero when using the idempotent produ
- Cannot set a transactional.id without also enabling idempote
- Must set acks to all in order to use the idempotent producer
AI-assisted analysis of apache/kafka@c31c9215e1 (2026-08-03).
Data as JSON: /data/errors/442702514354e7b2.json.
Report an issue: GitHub.