apache/beam · error · IllegalStateException

The first record [starting at %d] must be at a split point

Error message

The first record [starting at %d] must be at a split point

What it means

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.

Source

Thrown at sdks/java/core/src/main/java/org/apache/beam/sdk/io/range/OffsetRangeTracker.java:88

  @Override
  public synchronized Long getStartPosition() {
    return startOffset;
  }

  @Override
  public synchronized Long getStopPosition() {
    return stopOffset;
  }

  @Override
  public boolean tryReturnRecordAt(boolean isAtSplitPoint, Long recordStart) {
    return tryReturnRecordAt(isAtSplitPoint, recordStart.longValue());
  }

  public synchronized boolean tryReturnRecordAt(boolean isAtSplitPoint, long recordStart) {
    if (!isStarted() && !isAtSplitPoint) {
      throw new IllegalStateException(
          String.format("The first record [starting at %d] must be at a split point", recordStart));
    }
    if (recordStart < startOffset) {
      throw new IllegalStateException(
          String.format(
              "Trying to return record [starting at %d] which is before the start offset [%d]",
              recordStart, startOffset));
    }
    if (recordStart < lastRecordStart) {
      throw new IllegalStateException(
          String.format(
              "Trying to return record [starting at %d] "
                  + "which is before the last-returned record [starting at %d]",
              recordStart, lastRecordStart));
    }

    if (lastRecordStart == -1) {
      startOffset = recordStart;

View on GitHub (pinned to 12126d8942)

Solutions

  1. Ensure the reader positions itself at a record/split boundary before claiming the first record (seek to the next delimiter after startOffset).
  2. Report isAtSplitPoint=true for the first record if the reader genuinely begins at a record boundary (the common case at startOffset).
  3. Fix the split logic so split positions always fall on record boundaries (e.g. split only at newline positions).

Example fix

// before
tracker.tryReturnRecordAt(false, firstRecordStart); // first claim mid-record

// after
long boundary = findNextRecordStart(startOffset); // seek to split point
tracker.tryReturnRecordAt(true, boundary);
Defensive patterns

Strategy: validation

Validate before calling

checkState(tracker.isStarted() || isAtSplitPoint, "first claimed record must be at a split point");

Try / catch

try {
  tracker.tryReturnRecordAt(isAtSplitPoint, recordStart);
} catch (IllegalStateException e) {
  // reposition reader to the next record boundary and retry
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of apache/beam@12126d8942 (2026-09-13). Data as JSON: /api/errors/82dc9286b09af10c. Report an issue: GitHub.