{"id":"c597cf296973bd6c","repo":"apache/kafka","slug":"invalid-negative-offset","errorCode":null,"errorMessage":"Invalid negative offset","messagePattern":"Invalid negative offset","errorType":"validation","errorClass":"java.lang.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/c31c9215e131f8c17e79f8901b48c13ee6aa8e7a/clients/src/main/java/org/apache/kafka/clients/consumer/OffsetAndMetadata.java#L34-L70","documentation":"IllegalArgumentException from the OffsetAndMetadata constructor when the offset to be committed is negative. Offsets are non-negative positions in a partition; a negative value cannot represent a real committed position and would corrupt offset state on the broker.","triggerScenarios":"Calling new OffsetAndMetadata(offset, ...) or KafkaConsumer.commitSync with an offset that is -1 or otherwise negative, often because a lookup returned a sentinel/no-offset value.","commonSituations":"Using -1 or a NOT_FOUND sentinel to mean \"no offset yet\" and passing it directly; arithmetic that underflows when position() is unavailable (e.g. no assignment, empty partition); converting an unset long offset without a guard.","solutions":["Guard the offset before constructing OffsetAndMetadata: skip commit if offset < 0.","Replace sentinel -1 logic with OptionalLong.empty() / a no-op commit path when no offset exists.","Trace where the negative value originates (position(), seek(), or a manual offset map) and correct the source."],"exampleFix":"// before\nconsumer.commitSync(Collections.singletonMap(tp,\n    new OffsetAndMetadata(currentPosition))); // currentPosition == -1\n\n// after\nif (currentPosition >= 0) {\n    consumer.commitSync(Collections.singletonMap(tp,\n        new OffsetAndMetadata(currentPosition)));\n}","handlingStrategy":"validation","validationCode":"// OffsetAndMetadata requires offset >= 0; clamp or reject negatives upstream\nif (offset < 0) {\n    throw new IllegalArgumentException(\"Cannot commit a negative offset (got \" + offset + \")\");\n}\nconsumer.commitSync(Collections.singleton(tp, new OffsetAndMetadata(offset, leaderEpoch, metadata)));","typeGuard":null,"tryCatchPattern":"try {\n    new OffsetAndMetadata(offset, leaderEpoch, metadata);\n} catch (IllegalArgumentException e) {\n    if (\"Invalid negative offset\".equals(e.getMessage())) {\n        // skip the commit for this partition or recompute offset from consumer.position(tp)\n    }\n    throw e;\n}","preventionTips":["Always derive committed offsets from consumer.position(tp) rather than from arithmetic on stored values.","If you compute offsets (e.g. position - 1), guard the result against going below zero.","Treat a negative offset as a rewind/state-loss signal — do not silently clamp it."],"tags":["consumer","offset","commit","validation"],"analyzedSha":"c31c9215e131f8c17e79f8901b48c13ee6aa8e7a","analyzedAt":"2026-08-03T12:34:05.770Z","schemaVersion":2}