apache/iceberg · error · IllegalStateException
V2 positional delete file %s attached to main data file %s;
Error message
V2 positional delete file %s attached to main data file %s; the converter expects a V3 target with deletion vectors only.
What it means
loadExistingDVs collects the deletion vectors attached to a main data file and hard-fails if it finds a V2 positional delete file instead. The equality-delete converter assumes the target table is format V3 with deletion vectors only; a positional delete attached to a main data file breaks that assumption and is thrown as IllegalStateException naming both files.
Source
Thrown at flink/v2.1/flink/src/main/java/org/apache/iceberg/flink/maintenance/operator/EqualityConvertReader.java:230
@Override
public void close() throws Exception {
super.close();
tableLoader.close();
}
private Schema appendRowPosition(Schema schema) {
List<Types.NestedField> columns = Lists.newArrayList(schema.columns());
columns.add(MetadataColumns.ROW_POSITION);
return new Schema(columns);
}
private PositionDeleteIndex loadExistingDVs(FileScanTask task, String dataFilePath) {
List<DeleteFile> dvs = Lists.newArrayList();
for (DeleteFile deleteFile : task.deletes()) {
if (ContentFileUtil.isDV(deleteFile)) {
dvs.add(deleteFile);
} else if (deleteFile.content() == FileContent.POSITION_DELETES) {
throw new IllegalStateException(
String.format(
"V2 positional delete file %s attached to main data file %s; "
+ "the converter expects a V3 target with deletion vectors only.",
deleteFile.location(), dataFilePath));
} else if (deleteFile.content() == FileContent.EQUALITY_DELETES && !stagingOnTargetBranch) {
// When stagingBranch == targetBranch the target carries unconverted equality deletes; they
// are indexed as rows here and converted via the planner's RESOLVE_DELETE commands. On a
// separate target branch an attached equality delete means an unconverted delete leaked
// onto the target, which the converter cannot reason about.
throw new IllegalStateException(
String.format(
"Equality delete file %s attached to main data file %s; the converter expects "
+ "equality deletes only on the staging branch, converted to DVs on the target.",
deleteFile.location(), dataFilePath));
}
}
if (dvs.isEmpty()) {View on GitHub (pinned to 86d9c8fc54)
Solutions
- Run a rewrite/delete-file compaction that removes legacy positional deletes (converts them to DVs) before enabling the equality converter.
- Verify table format version is 3 and that no writer with format-version 2 is appending to the target branch during maintenance.
- Point the converter at a branch that only receives V3/DV writes, or first fully rewrite affected data files.
Example fix
// before
// table.formatVersion()==2, positional deletes attached
TableScan scan = table.newScan();
// after
Table upgraded = ...; // migrate to format version 3 first
Preconditions.checkArgument(upgraded.ops().current().formatVersion() >= 3,
"Equality converter requires format version >= 3"); Defensive patterns
Strategy: validation
Validate before calling
Preconditions.checkArgument(
table.ops().current().formatVersion() >= 3,
"Equality converter requires format version 3+ with deletion vectors");
// optionally scan once and assert no POSITION_DELETES deletes are attached Try / catch
try {
loadExistingDVs(task, dataFilePath);
} catch (IllegalStateException e) {
LOG.error("Legacy V2 positional deletes present: {}", e.getMessage());
// run compaction that converts positional deletes to DVs first
} Prevention
- Migrate the table to format version 3 and rewrite away legacy positional deletes before enabling the converter
- Ensure no format-version-2 writers commit to the target branch during maintenance
- Monitor table deletes() composition in tests before scheduling conversion
When it happens
Trigger: A data file scan task on the target/main branch has task.deletes() containing a DeleteFile with content == FileContent.POSITION_DELETES, i.e. the table still has V2-style .pos deletes when the converter runs.
Common situations: Table was upgraded to format version 3 but legacy positional deletes from V2 writes are still attached; another writer (older engine version) keeps producing positional deletes on the same branch while maintenance runs.
Understand the failure class
Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.
Related errors
- %s does not have a format version
- Staging snapshot %s on branch '%s' contains a V2 positional
- V2 positional delete file %s attached to main data file %s;
- Staging snapshot %s on branch '%s' contains a V2 positional
- V2 positional delete file %s attached to main data file %s;
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/d7c0f09bd2cdab74.
Report an issue: GitHub.