{"record":{"id":"82dc9286b09af10c","repo":"apache/beam","slug":"the-first-record-starting-at-d-must-be-at-a-split-point","errorCode":null,"errorMessage":"The first record [starting at %d] must be at a split point","messagePattern":"The first record \\[starting at (.+?)\\] must be at a split point","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"sdks/java/core/src/main/java/org/apache/beam/sdk/io/range/OffsetRangeTracker.java","lineNumber":88,"sourceCode":"\n  @Override\n  public synchronized Long getStartPosition() {\n    return startOffset;\n  }\n\n  @Override\n  public synchronized Long getStopPosition() {\n    return stopOffset;\n  }\n\n  @Override\n  public boolean tryReturnRecordAt(boolean isAtSplitPoint, Long recordStart) {\n    return tryReturnRecordAt(isAtSplitPoint, recordStart.longValue());\n  }\n\n  public synchronized boolean tryReturnRecordAt(boolean isAtSplitPoint, long recordStart) {\n    if (!isStarted() && !isAtSplitPoint) {\n      throw new IllegalStateException(\n          String.format(\"The first record [starting at %d] must be at a split point\", recordStart));\n    }\n    if (recordStart < startOffset) {\n      throw new IllegalStateException(\n          String.format(\n              \"Trying to return record [starting at %d] which is before the start offset [%d]\",\n              recordStart, startOffset));\n    }\n    if (recordStart < lastRecordStart) {\n      throw new IllegalStateException(\n          String.format(\n              \"Trying to return record [starting at %d] \"\n                  + \"which is before the last-returned record [starting at %d]\",\n              recordStart, lastRecordStart));\n    }\n\n    if (lastRecordStart == -1) {\n      startOffset = recordStart;","sourceCodeStart":70,"sourceCodeEnd":106,"githubUrl":"https://github.com/apache/beam/blob/12126d8942aaf848030c478b4c6a28c6af861c66/sdks/java/core/src/main/java/org/apache/beam/sdk/io/range/OffsetRangeTracker.java#L70-L106","documentation":"OffsetRangeTracker.tryReturnRecordAt enforces that the very first record claimed by a reader starts exactly at a split point (i.e. the reader was positioned at the beginning of its assigned range). If the tracker has not started and the record is claimed as not being at a split point, the range model is broken — the runner could not tell whether the record was already processed — so it fails fast with IllegalStateException.","triggerScenarios":"A source reader calls tryReturnRecordAt(false, recordStart) for the first record after the tracker was created (start() path before any record was returned), i.e. claiming an initial record that is not at position startOffset / not at a split boundary.","commonSituations":"Custom offset-based sources whose reader begins mid-record after a split/resume (e.g. a file source that splits at byte offsets without splitting at newline boundaries); restoring from a checkpoint at an offset that lands inside a record; tests constructing trackers and immediately claiming non-split-point records.","solutions":["Ensure the reader positions itself at a record/split boundary before claiming the first record (seek to the next delimiter after startOffset).","Report isAtSplitPoint=true for the first record if the reader genuinely begins at a record boundary (the common case at startOffset).","Fix the split logic so split positions always fall on record boundaries (e.g. split only at newline positions)."],"exampleFix":"// before\ntracker.tryReturnRecordAt(false, firstRecordStart); // first claim mid-record\n\n// after\nlong boundary = findNextRecordStart(startOffset); // seek to split point\ntracker.tryReturnRecordAt(true, boundary);","handlingStrategy":"validation","validationCode":"checkState(tracker.isStarted() || isAtSplitPoint, \"first claimed record must be at a split point\");","typeGuard":null,"tryCatchPattern":"try {\n  tracker.tryReturnRecordAt(isAtSplitPoint, recordStart);\n} catch (IllegalStateException e) {\n  // reposition reader to the next record boundary and retry\n}","preventionTips":["Position readers at record boundaries (split points) before the first claim.","Report the first claimed record with isAtSplitPoint=true when starting at startOffset.","Design split logic so offsets always fall on record delimiters."],"tags":["java","apache-beam","io","range-tracker","splittable"],"backgroundTag":"invalid-state-transition","analyzedSha":"12126d8942aaf848030c478b4c6a28c6af861c66","analyzedAt":"2026-09-13T01:50:10.254Z","contentChangedAt":"2026-09-13T01:50:10.254Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}