{"record":{"id":"476b3df307695e3b","repo":"apache/hadoop","slug":"cannot-seek-to-a-negative-offset-476b3d","errorCode":null,"errorMessage":"Cannot seek to a negative offset","messagePattern":"Cannot seek to a negative offset","errorType":"exception","errorClass":"EOFException","httpStatus":null,"severity":"error","filePath":"hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/sftp/SFTPInputStream.java","lineNumber":64,"sourceCode":"  SFTPInputStream(ChannelSftp channel, Path path, FileSystem.Statistics stats)\n      throws IOException {\n    try {\n      this.channel = channel;\n      this.path = path;\n      this.stats = stats;\n      this.wrappedStream = channel.get(path.toUri().getPath());\n      SftpATTRS stat = channel.lstat(path.toString());\n      this.contentLength = stat.getSize();\n    } catch (SftpException e) {\n      throw new IOException(e);\n    }\n  }\n\n  @Override\n  public synchronized void seek(long position) throws IOException {\n    checkNotClosed();\n    if (position < 0) {\n      throw new EOFException(FSExceptionMessages.NEGATIVE_SEEK);\n    }\n    nextPos = position;\n  }\n\n  @Override\n  public synchronized int available() throws IOException {\n    checkNotClosed();\n    long remaining = contentLength - nextPos;\n    if (remaining > Integer.MAX_VALUE) {\n      return Integer.MAX_VALUE;\n    }\n    return (int) remaining;\n  }\n\n  private void seekInternal() throws IOException {\n    if (pos == nextPos) {\n      return;\n    }","sourceCodeStart":46,"sourceCodeEnd":82,"githubUrl":"https://github.com/apache/hadoop/blob/2add9630210752f88ceb1bb74eb65e37bf41da8e/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/sftp/SFTPInputStream.java#L46-L82","documentation":"SFTPInputStream.seek(long position) throws EOFException carrying FSExceptionMessages.NEGATIVE_SEEK ('Cannot seek to a negative offset') when position < 0. Note the exception type is EOFException, not IllegalArgumentException — the stream follows the FileSystem convention that seek positions must be non-negative.","triggerScenarios":"Calling seek(negative) directly, or computing a position that underflows: seek(pos - n) where n > pos, first-split start offsets computed as -1, and rewind/skip arithmetic with off-by-one errors.","commonSituations":"Custom line readers that recompute offsets after reading a trailing newline; input-format split logic where the first record's start is calculated before position 0; porting seek logic from a stream that permitted negative values or wrapped them.","solutions":["Clamp the computed position before seeking: long target = Math.max(0, pos - n);","Fix the underlying offset arithmetic — usually an off-by-one or a missing guard for the first record/split (start == 0).","Use seek(0) when the intent is to reset to the beginning of the stream."],"exampleFix":"// before\nstream.seek(pos - bytesToSkip); // throws when bytesToSkip > pos\n\n// after\nstream.seek(Math.max(0, pos - bytesToSkip));","handlingStrategy":"validation","validationCode":"long target = Math.max(0, computedOffset);\nstream.seek(target);","typeGuard":null,"tryCatchPattern":"try {\n  stream.seek(pos);\n} catch (EOFException e) {\n  // negative position: fix offset arithmetic, then clamp and retry at 0\n  stream.seek(0);\n}","preventionTips":["Clamp every computed seek position to >= 0 before calling seek().","Guard split-start arithmetic for the first record (start == 0).","Remember the exception type here is EOFException, not IllegalArgumentException."],"tags":["sftp","seek","negative-offset","input-stream","hadoop"],"backgroundTag":"invalid-seek-offset","analyzedSha":"2add9630210752f88ceb1bb74eb65e37bf41da8e","analyzedAt":"2026-08-22T19:55:07.957Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}