prestodb/presto · error · IllegalStateException

field %s has unexpected position count. Expected: %s, actual

Error message

field %s has unexpected position count. Expected: %s, actual: %s

What it means

RowBlockBuilder tracks each field's element count in fieldBlockOffsets. When a row entry is added, the offset recorded for the new position must equal the actual position count of each field's block builder. If they diverge, the builder's internal offsets are inconsistent with the underlying field blocks, so the library throws to prevent producing a corrupt row block.

Source

Thrown at presto-common/src/main/java/com/facebook/presto/common/block/RowBlockBuilder.java:238

        if (rowIsNull.length <= positionCount) {
            int newSize = BlockUtil.calculateNewArraySize(rowIsNull.length);
            rowIsNull = Arrays.copyOf(rowIsNull, newSize);
            fieldBlockOffsets = Arrays.copyOf(fieldBlockOffsets, newSize + 1);
        }

        if (isNull) {
            fieldBlockOffsets[positionCount + 1] = fieldBlockOffsets[positionCount];
        }
        else {
            fieldBlockOffsets[positionCount + 1] = fieldBlockOffsets[positionCount] + 1;
        }
        rowIsNull[positionCount] = isNull;
        hasNullRow |= isNull;
        positionCount++;

        for (int i = 0; i < numFields; i++) {
            if (fieldBlockBuilders[i].getPositionCount() != fieldBlockOffsets[positionCount]) {
                throw new IllegalStateException(format("field %s has unexpected position count. Expected: %s, actual: %s", i, fieldBlockOffsets[positionCount], fieldBlockBuilders[i].getPositionCount()));
            }
        }

        if (blockBuilderStatus != null) {
            blockBuilderStatus.addBytes(Integer.BYTES + Byte.BYTES);
        }
    }

    @Override
    public Block build()
    {
        if (currentEntryOpened) {
            throw new IllegalStateException("Current entry must be closed before the block can be built");
        }
        Block[] fieldBlocks = new Block[numFields];
        for (int i = 0; i < numFields; i++) {
            fieldBlocks[i] = fieldBlockBuilders[i].build();
        }

View on GitHub (pinned to 55bb57d202)

Solutions

  1. Verify every fieldBlockBuilders[i] receives exactly one value (or one appendNull) per row entry
  2. Fix loops that write more than one element per field per row
  3. On deserialization, ensure block.getPositionCount() matches the expected field offsets before closeEntry
  4. Never appendNull on an individual field builder inside an opened entry if the row itself already tracks nulls

Example fix

// before
BlockBuilder entry = rowBuilder.beginBlockEntry();
entry.appendNull();
entry.appendNull(); // two positions in one field -> offsets mismatch
rowBuilder.closeEntry();
// after
BlockBuilder entry = rowBuilder.beginBlockEntry();
entry.appendNull(); // exactly one value per field per row
rowBuilder.closeEntry();
Defensive patterns

Strategy: validation

Validate before calling

// per row entry, ensure every field got exactly one value
// after writing the row but before closeEntry():
for (int i = 0; i < fieldBlockBuilders.length; i++) {
    // each field must advance exactly once per row
    checkState(fieldBlockBuilders[i].getPositionCount() == expectedPositions,
        "field %s advanced %s times, expected %s", i, fieldBlockBuilders[i].getPositionCount(), expectedPositions);
}

Try / catch

try {
    rowBuilder.closeEntry();
} catch (IllegalStateException e) {
    if (e.getMessage().contains("unexpected position count")) {
        throw new IllegalStateException("wrote wrong number of values into a row field; check field write loop", e);
    }
    throw e;
}

Prevention

When it happens

Trigger: Writing a different number of positions to a field builder than the row expects — e.g. calling fieldBlockBuilder.appendNull() or writing multiple values into one field within a single row entry, or calling appendStructure with a block that populates fields unevenly, then closeEntry().

Common situations: Hand-written row type serialization/deserialization where each column must contribute exactly one value per row; code that mistakenly writes two values (like writing a slice plus its length) into a single field entry; copying logic that skips a field on some branch.

Related errors


AI-assisted analysis of prestodb/presto@55bb57d202 (2026-09-04). Data as JSON: /api/errors/044c0fcfd6485735. Report an issue: GitHub.