{"record":{"id":"90831947e1c35a99","repo":"apache/hadoop","slug":"cannot-seek-to-negative-offset","errorCode":null,"errorMessage":"Cannot seek to negative offset","messagePattern":"Cannot seek to negative offset","errorType":"exception","errorClass":"EOFException","httpStatus":null,"severity":"error","filePath":"hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/DFSInputStream.java","lineNumber":1645,"sourceCode":"      if (n+curPos > fileLen) {\n        n = fileLen - curPos;\n      }\n      seek(curPos+n);\n      return n;\n    }\n    return n < 0 ? -1 : 0;\n  }\n\n  /**\n   * Seek to a new arbitrary location\n   */\n  @Override\n  public synchronized void seek(long targetPos) throws IOException {\n    if (targetPos > getFileLength()) {\n      throw new EOFException(\"Cannot seek after EOF\");\n    }\n    if (targetPos < 0) {\n      throw new EOFException(\"Cannot seek to negative offset\");\n    }\n    if (closed.get()) {\n      throw new IOException(\"Stream is closed!\");\n    }\n    boolean done = false;\n    if (pos <= targetPos && targetPos <= blockEnd) {\n      //\n      // If this seek is to a positive position in the current\n      // block, and this piece of data might already be lying in\n      // the TCP buffer, then just eat up the intervening data.\n      //\n      int diff = (int)(targetPos - pos);\n      if (diff <= blockReader.available()) {\n        try {\n          pos += blockReader.skip(diff);\n          if (pos == targetPos) {\n            done = true;\n          } else {","sourceCodeStart":1627,"sourceCodeEnd":1663,"githubUrl":"https://github.com/apache/hadoop/blob/2add9630210752f88ceb1bb74eb65e37bf41da8e/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/DFSInputStream.java#L1627-L1663","documentation":"DFSInputStream.seek(long) validates the requested position before doing anything: any targetPos < 0 throws java.io.EOFException('Cannot seek to negative offset'). HDFS files start at offset 0, so a negative target is always a caller bug (bad offset arithmetic), not a transient cluster condition. Note the odd choice of EOFException, which pairs it with the 'Cannot seek after EOF' check just above it.","triggerScenarios":"Calling fsDataInputStream.seek(n) with n negative, e.g. seek(getPos() - delta) where delta > getPos(), or seek(-1). It is NOT produced by skip(): skip() clamps positive n to the file length before delegating to seek(), so only direct seek() calls with an under-flowing computed offset hit it.","commonSituations":"Record readers that compute a previous-record start as (currentPos - recordLength) and underflow at the first record; offsets parsed from corrupted index/footer metadata; index rebuild tools that subtract a header size larger than the current position; ported code that assumed skip()/seek() silently accept negative values.","solutions":["Fix the offset arithmetic so it can never go below 0: clamp with Math.max(0, computedPos) before calling seek().","Audit every expression feeding seek() for subtraction that can underflow (pos - headerLen, splitStart - 1, pos - recordLen) and guard each one.","If the negative value comes from parsed metadata, validate it and fail fast with a clear message about the corrupt index instead of letting seek() throw.","Add a boundary unit test at position 0 and position 1 to lock in the clamping behavior."],"exampleFix":"// before\nlong prevStart = in.getPos() - recordHeaderLen;\nin.seek(prevStart); // throws when getPos() < recordHeaderLen\n\n// after\nlong prevStart = Math.max(0, in.getPos() - recordHeaderLen);\nin.seek(prevStart);","handlingStrategy":"validation","validationCode":"long targetPos = computedOffset; // whatever the caller computed\nlong fileLen = fs.getFileStatus(path).getLen();\nif (targetPos < 0 || targetPos > fileLen) {\n  throw new IllegalArgumentException(\n      \"seek target \" + targetPos + \" outside [0, \" + fileLen + \"] for \" + path);\n}\nin.seek(targetPos);","typeGuard":null,"tryCatchPattern":"try {\n  in.seek(targetPos);\n} catch (EOFException e) {\n  // negative or past-EOF target: programming error in offset math - fail loudly, do not retry\n  throw new IllegalArgumentException(\"bad seek offset \" + targetPos, e);\n}","preventionTips":["Derive every seek offset with Math.max(0, ...) when it comes from a subtraction.","Validate offsets parsed from index/ footer metadata before use.","Unit-test boundary offsets 0, 1, and fileLen.","Remember skip() clamps but seek() does not - never feed seek() raw computed negatives."],"tags":["hdfs","hdfs-client","seek","offset-validation","io"],"backgroundTag":"negative-offset-seek","analyzedSha":"2add9630210752f88ceb1bb74eb65e37bf41da8e","analyzedAt":"2026-08-22T19:55:07.957Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}