apache/kafka · error · IllegalArgumentException
Invalid serialized transaction state format: ${serializedSta
Error message
Invalid serialized transaction state format: ${serializedState} What it means
Thrown by the PreparedTxnState(String) constructor as an IllegalArgumentException when the serialized string cannot be parsed into the expected "producerId:epoch" form. The constructor splits on ':' and requires exactly two non-negative numeric parts; a wrong part count, non-numeric content, or negative values all funnel into this message (the NumberFormatException from Long.parseLong/Short.parseShort is caught and rethrown as the same IllegalArgumentException at line 67). It guards deserialization of prepared-transaction state shared across process restarts or test fixtures.
Source
Thrown at clients/src/main/java/org/apache/kafka/clients/producer/PreparedTxnState.java:55
}
/**
* Creates a new PreparedTxnState from a serialized string representation
*
* @param serializedState The serialized string to deserialize.
* @throws IllegalArgumentException if the serialized string is not in the expected format
*/
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
*View on GitHub (pinned to c31c9215e1)
Solutions
- Produce the serialized form only via PreparedTxnState.toString() (format "producerId:epoch", or empty for uninitialized) so the round-trip is guaranteed.
- Validate the input before constructing: non-empty, matches regex ^\d+:\d+$, and both values >= 0; reject early with a domain-meaningful error.
- If loading from persistent storage, wrap the constructor call in try/catch(IllegalArgumentException) and treat the record as uninitialized (use the no-arg PreparedTxnState() or pass empty string).
- Fix the source of the malformed value (fixture/config/serialization layer) rather than silently defaulting, to avoid masking real corruption.
Example fix
// before
PreparedTxnState s = new PreparedTxnState("1000-1"); // IllegalArgumentException
// after
PreparedTxnState s = new PreparedTxnState("1000:1");
// or round-trip safely:
String stored = existingState.toString();
PreparedTxnState s = stored.isEmpty() ? new PreparedTxnState() : new PreparedTxnState(stored); Defensive patterns
Strategy: validation
Validate before calling
import java.util.regex.Pattern;
static final Pattern STATE_RE = Pattern.compile("^\\d+:\\d+$");
boolean isValidSerializedState(String s) {
if (s == null || s.isEmpty()) return true; // empty == uninitialized, allowed
if (!STATE_RE.matcher(s).matches()) return false;
String[] parts = s.split(":");
try {
long pid = Long.parseLong(parts[0]);
short ep = Short.parseShort(parts[1]);
return pid >= 0 && ep >= 0;
} catch (NumberFormatException e) {
return false;
}
}
// usage:
if (!isValidSerializedState(input)) {
throw new IllegalArgumentException("Bad state string: " + input);
}
PreparedTxnState state = new PreparedTxnState(input); Try / catch
try {
PreparedTxnState state = new PreparedTxnState(serialized);
} catch (IllegalArgumentException e) {
if (e.getMessage().startsWith("Invalid serialized transaction state format")) {
// log and reject the input; do not proceed with a default state
} else {
throw e;
}
} Prevention
- Only feed strings produced by PreparedTxnState.toString() back into the String constructor; round-trip your own output.
- Validate the 'producerId:epoch' format with a regex before constructing.
- Treat null/empty as a deliberate 'uninitialized' marker; reject everything else that fails the regex.
- Never accept serialized state from untrusted input — it is a compact, single-colon pair only.
When it happens
Trigger: Constructing new PreparedTxnState(serialized) where serialized is non-empty and (a) does not contain exactly one ':' (so parts.length != 2), (b) either side fails Long.parseLong/Short.parseShort, or (c) parses but producerId<0 or epoch<0. Reached at line 55 (format mismatch) or line 67 (numeric failure).
Common situations: Persisting PreparedTxnState.toString() and loading it back with a corrupt/truncated value; a test fixture or config file providing a malformed string like "1000" or "1000:1:0" or "abc:2"; cross-version mismatch where an older format is read by newer code that expects exactly two fields; user-supplied input passed straight into the constructor without validation.
Related errors
- Invalid url in bootstrap.servers: {url}
- Invalid url in bootstrap.servers: {url}
- Invalid port in bootstrap.servers: {url}
- When the security.protocol configuration enables SASL, mecha
- Invalid leader epoch {leaderEpoch} (must be non-negative)
AI-assisted analysis of apache/kafka@c31c9215e1 (2026-08-03).
Data as JSON: /data/errors/e0dd0afe9f54d95a.json.
Report an issue: GitHub.