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
- Verify every fieldBlockBuilders[i] receives exactly one value (or one appendNull) per row entry
- Fix loops that write more than one element per field per row
- On deserialization, ensure block.getPositionCount() matches the expected field offsets before closeEntry
- 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
- Write exactly one value or one appendNull per field per row entry
- Never call appendNull twice on one field builder inside a single entry
- Count field writes in loops copying multi-field rows
- Unit-test row writers with nested and null fields
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
- Current entry must be closed before a null can be written
- Current entry must be closed before the block can be built
- reference to a non-existent key
- Number of fields in RowBlock must be positive
- Invalid field position selection after nulls removed: " + se
AI-assisted analysis of prestodb/presto@55bb57d202 (2026-09-04).
Data as JSON: /api/errors/044c0fcfd6485735.
Report an issue: GitHub.