apache/seatunnel · error · IllegalStateException

no more rows to read.

Error message

no more rows to read.

What it means

ArrowToSeatunnelRowReader.next() guards on hasNext(); calling next() after all rows of the current row batch are consumed throws IllegalStateException("no more rows to read."). Callers must check hasNext() before each next() call — it's a strict iterator-contract violation, not a data problem.

Solutions

  1. Wrap the read loop with hasNext(): `while (reader.hasNext()) { row = reader.next(); }`.
  2. If you need more rows, load/advance the next row batch rather than calling next() again on the exhausted one.
  3. Audit retry/re-read logic so a re-read starts from a fresh reader or resets the offset.
  4. Check loop bounds — use hasNext() instead of a hard-coded count that can drift from readRowCount.

Example fix

// before
for (int i = 0; i < size; i++) { rows.add(reader.next()); } // size may exceed batch rows
// after
while (reader.hasNext()) { rows.add(reader.next()); }
Defensive patterns

Strategy: type-guard

Type guard

// Java narrowing: always gate next() on hasNext()
if (!reader.hasNext()) {
    return null; // batch exhausted — advance to next batch instead
}

Try / catch

try {
    row = reader.next();
} catch (IllegalStateException e) {
    LOG.warn("row batch exhausted; loading next batch");
    loadNextBatch();
}

Prevention

When it happens

Trigger: Invoking next() when offsetInRowBatch >= readRowCount — i.e. looping past the row count of the loaded Arrow row batch, typically a loop missing a hasNext() check or reading after the batch was exhausted.

Common situations: Custom code iterating ArrowSourceReader results without hasNext(); a retry path re-reading rows after exhaustion; off-by-one in a for-loop bound vs. the batch's row count.

Related errors


AI-assisted analysis of apache/seatunnel@cf67b549a7 (2026-09-10). Data as JSON: /api/errors/71a950ba98f2a592. Report an issue: GitHub.

Appendix: source

Thrown at seatunnel-connectors-v2/connector-common/src/main/java/org/apache/seatunnel/connectors/seatunnel/common/source/arrow/reader/ArrowToSeatunnelRowReader.java:128

                }
                convertSeatunnelRow();
                this.readRowCount += root.getRowCount();
            }
            return this;
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            close();
        }
    }

    public boolean hasNext() {
        return offsetInRowBatch < readRowCount;
    }

    public SeaTunnelRow next() {
        if (!hasNext()) {
            throw new IllegalStateException("no more rows to read.");
        }
        return seatunnelRowBatch.get(offsetInRowBatch++);
    }

    private void convertSeatunnelRow() {
        for (FieldVector fieldVector : fieldVectors) {
            String name = fieldVector.getField().getName();
            Integer fieldIndex = fieldIndexMap.get(name);
            Types.MinorType minorType = fieldVector.getMinorType();
            for (int i = 0; i < seatunnelRowBatch.size(); i++) {
                // arrow field not in the Seatunnel Schema field, skip it
                if (fieldIndex != null) {
                    SeaTunnelDataType<?> seaTunnelDataType = seaTunnelDataTypes[fieldIndex];
                    Object fieldValue =
                            convertArrowData(
                                    readRowCount + i, minorType, fieldVector, seaTunnelDataType);
                    fieldValue =
                            convertSeatunnelRowValue(

View on GitHub (pinned to cf67b549a7)