{"record":{"id":"c597cf296973bd6c","repo":"apache/kafka","slug":"invalid-negative-offset","errorCode":null,"errorMessage":"Invalid negative offset","messagePattern":"Invalid negative offset","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"clients/src/main/java/org/apache/kafka/clients/consumer/OffsetAndMetadata.java","lineNumber":52,"sourceCode":"\n    private final long offset;\n    private final String metadata;\n\n    // We use null to represent the absence of a leader epoch to simplify serialization.\n    // I.e., older serializations of this class which do not have this field will automatically\n    // initialize its value to null.\n    private final Integer leaderEpoch;\n\n    /**\n     * Construct a new OffsetAndMetadata object for committing through {@link KafkaConsumer}.\n     *\n     * @param offset The offset to be committed\n     * @param leaderEpoch Optional leader epoch of the last consumed record\n     * @param metadata Non-null metadata\n     */\n    public OffsetAndMetadata(long offset, Optional<Integer> leaderEpoch, String metadata) {\n        if (offset < 0)\n            throw new IllegalArgumentException(\"Invalid negative offset\");\n\n        this.offset = offset;\n        this.leaderEpoch = leaderEpoch.orElse(null);\n\n        // The server converts null metadata to an empty string. So we store it as an empty string as well on the client\n        // to be consistent.\n        this.metadata = Objects.requireNonNullElse(metadata, OffsetFetchResponse.NO_METADATA);\n    }\n\n    /**\n     * Construct a new OffsetAndMetadata object for committing through {@link KafkaConsumer}.\n     * @param offset The offset to be committed\n     * @param metadata Non-null metadata\n     */\n    public OffsetAndMetadata(long offset, String metadata) {\n        this(offset, Optional.empty(), metadata);\n    }\n","sourceCodeStart":34,"sourceCodeEnd":70,"githubUrl":"https://github.com/apache/kafka/blob/996fb4585aa1bcc8980b0e1b8d6b168b986cd979/clients/src/main/java/org/apache/kafka/clients/consumer/OffsetAndMetadata.java#L34-L70","documentation":"OffsetAndMetadata is the value object stored for each partition when committing offsets (and serialized in OffsetCommit requests). The constructor enforces offset >= 0 because negative offsets are not representable on the broker and would corrupt committed state. The check throws IllegalArgumentException before any field is assigned.","triggerScenarios":"new OffsetAndMetadata(-1, ...), or any construction path where the offset arithmetic underflows (e.g., position-1 when position==0, or a sentinel value of -1 used to mean 'no offset').","commonSituations":"Off-by-one in 'last consumed offset + 1' logic; passing a 'not found' sentinel (-1) from a custom store into commitSync; replaying offsets read from an external system that uses -1 for 'unknown'.","solutions":["Validate offset >= 0 before constructing OffsetAndMetadata; skip the commit for that partition if offset is unknown.","Replace sentinel -1 semantics with Optional<Long> / null and only construct OffsetAndMetadata when present.","If committing KafkaConsumer.position() directly, ensure the consumer has a position (position() returns >= 0 once assigned and polled)."],"exampleFix":"// before\nlong off = position > 0 ? position - 1 : -1;\nconsumer.commitSync(Map.of(tp, new OffsetAndMetadata(off, \"\"))); // -> IllegalArgumentException when position==0\n\n// after\nif (position < 0) {\n    // skip commit, no position yet\n} else {\n    consumer.commitSync(Map.of(tp, new OffsetAndMetadata(position, \"\")));\n}","handlingStrategy":"validation","validationCode":"if (offset < 0) throw new IllegalStateException(\"no offset to commit for \" + tp);\nreturn new OffsetAndMetadata(offset, leaderEpoch, metadata);","typeGuard":"static boolean isCommittableOffset(long offset) { return offset >= 0; }","tryCatchPattern":"try { consumer.commitSync(Map.of(tp, new OffsetAndMetadata(off, \"\"))); }\ncatch (IllegalArgumentException e) { /* skip this partition's commit */ }","preventionTips":["Never use -1 as a sentinel; use Optional<Long> at your API boundary.","Validate offset >= 0 before constructing OffsetAndMetadata.","When wrapping KafkaConsumer.position(), guard against 'no position yet'."],"tags":["consumer","offset-commit","validation","kafka-clients"],"backgroundTag":null,"analyzedSha":"996fb4585aa1bcc8980b0e1b8d6b168b986cd979","analyzedAt":"2026-08-11T22:03:28.655Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}