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

  1. Produce the serialized form only via PreparedTxnState.toString() (format "producerId:epoch", or empty for uninitialized) so the round-trip is guaranteed.
  2. Validate the input before constructing: non-empty, matches regex ^\d+:\d+$, and both values >= 0; reject early with a domain-meaningful error.
  3. 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).
  4. 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

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


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