apache/kafka · error · IllegalArgumentException

Invalid message timestamp {}

Error message

Invalid message timestamp {}

What it means

Thrown by LegacyRecord.write() when serializing a legacy (v0/v1) message: a record timestamp must be either non-negative or exactly RecordBatch.NO_TIMESTAMP (the sentinel -1 that means 'no timestamp'). Any other negative value is rejected because the on-wire timestamp field is an unsigned 8-byte value and negative numbers are not representable/valid. The guard protects against writing corrupt frames that would later fail CRC or parsing.

Source

Thrown at clients/src/main/java/org/apache/kafka/common/record/internal/LegacyRecord.java:449

                             long timestamp,
                             byte[] key,
                             byte[] value) throws IOException {
        write(out, magic, crc, attributes, timestamp, wrapNullable(key), wrapNullable(value));
    }

    // Write a record to the buffer, if the record's compression type is none, then
    // its value payload should be already compressed with the specified type
    private static void write(DataOutputStream out,
                              byte magic,
                              long crc,
                              byte attributes,
                              long timestamp,
                              ByteBuffer key,
                              ByteBuffer value) throws IOException {
        if (magic != RecordBatch.MAGIC_VALUE_V0 && magic != RecordBatch.MAGIC_VALUE_V1)
            throw new IllegalArgumentException("Invalid magic value " + magic);
        if (timestamp < 0 && timestamp != RecordBatch.NO_TIMESTAMP)
            throw new IllegalArgumentException("Invalid message timestamp " + timestamp);

        // write crc
        out.writeInt((int) (crc & 0xffffffffL));
        // write magic value
        out.writeByte(magic);
        // write attributes
        out.writeByte(attributes);

        // maybe write timestamp
        if (magic > RecordBatch.MAGIC_VALUE_V0)
            out.writeLong(timestamp);

        // write the key
        if (key == null) {
            out.writeInt(-1);
        } else {
            int size = key.remaining();
            out.writeInt(size);

View on GitHub (pinned to c31c9215e1)

Solutions

  1. Set the timestamp to RecordBatch.NO_TIMESTAMP (-1L) when you mean 'no timestamp', not any other negative number.
  2. Ensure any timestamp arithmetic cannot produce a negative result; clamp to >=0 or NO_TIMESTAMP.
  3. If forwarding timestamps from an upstream schema, map its null/missing value to RecordBatch.NO_TIMESTAMP before calling LegacyRecord.write.

Example fix

// before
LegacyRecord.write(out, magic, crc, attributes, userTimestamp - offset, key, value);
// after
long ts = userTimestamp == null ? RecordBatch.NO_TIMESTAMP : Math.max(0, userTimestamp - offset);
LegacyRecord.write(out, magic, crc, attributes, ts, key, value);
Defensive patterns

Strategy: validation

Validate before calling

// Validate timestamp before serializing a LegacyRecord (magic v1).
// RecordBatch.NO_TIMESTAMP (-1) is the only allowed non-positive value.
long NO_TIMESTAMP = -1L;
if (timestamp < 0L && timestamp != NO_TIMESTAMP) {
    throw new IllegalArgumentException(
        "Rejecting record: timestamp " + timestamp + " must be >= 0 or NO_TIMESTAMP(-1)");
}

Type guard

// Timestamp must be a non-negative long, or the sentinel NO_TIMESTAMP(-1).
static boolean isValidLegacyTimestamp(long ts) {
    return ts >= 0L || ts == -1L;
}

Try / catch

try {
    // ... call API that serializes a LegacyRecord (e.g. MemoryRecordsBuilder.append) ...
} catch (IllegalArgumentException e) {
    if (e.getMessage() != null && e.getMessage().startsWith("Invalid message timestamp")) {
        // Drop / route-to-DLT the offending record; never silently retry with same ts.
        throw new RecordSerializationException("Bad producer timestamp", e);
    }
    throw e;
}

Prevention

When it happens

Trigger: Calling any of the LegacyRecord.write(...) overloads (or the higher-level MemoryRecordsBuilder path that targets magic=0/1) with a timestamp such as -2, Long.MIN_VALUE, or any negative value other than -1. Most commonly reached by tests or custom serializers that hand-craft records, or by mis-mapping a 'missing timestamp' sentinel to something other than NO_TIMESTAMP before writing.

Common situations: Reusing a sentinel constant (e.g. -1 from a different schema, or 0 interpreted as 'unset' then decremented), off-by-one timestamp math, arithmetic underflow when subtracting offsets, or a producer/serializer library that represents 'no timestamp' as a different negative value than Kafka's NO_TIMESTAMP.

Related errors


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