apache/iceberg · error · IllegalStateException
Staging snapshot %s on branch '%s' contains a V2 positional
Error message
Staging snapshot %s on branch '%s' contains a V2 positional delete file (%s); equality delete conversion expects a V3 staging branch written by Flink, which produces only deletion vectors for deletes.
What it means
Thrown by EqualityConvertPlanner.retrieveStagingFiles when a delete file in the staging snapshot is neither an equality delete nor a deletion vector, i.e. a V2 positional delete file. The converter requires a V3-format staging branch where Flink writes only DVs for deletes, so it fails fast rather than mishandling the positional delete.
Source
Thrown at flink/v2.3/flink/src/main/java/org/apache/iceberg/flink/maintenance/operator/EqualityConvertPlanner.java:584
for (DeleteFile deleteFile : changes.addedDeleteFiles()) {
if (deleteFile.content() == FileContent.EQUALITY_DELETES) {
Set<Integer> deleteFieldIds = Sets.newHashSet(deleteFile.equalityFieldIds());
Preconditions.checkState(
deleteFieldIds.equals(eqFieldIds),
"Staging snapshot %s on branch '%s' contains an equality delete file %s with "
+ "equalityFieldIds=%s, which does not match the configured eqFieldIds=%s. "
+ "The writer must use the same equality field IDs as the converter.",
stagingSnapshot.snapshotId(),
stagingBranch,
deleteFile.location(),
deleteFieldIds,
eqFieldIds);
validateDeleteSpecPartitionColumns(stagingSnapshot, deleteFile);
eqDeleteFiles.add(deleteFile);
} else if (ContentFileUtil.isDV(deleteFile)) {
stagingDVFiles.add(deleteFile);
} else {
throw new IllegalStateException(
String.format(
"Staging snapshot %s on branch '%s' contains a V2 positional delete file (%s); "
+ "equality delete conversion expects a V3 staging branch written by Flink, "
+ "which produces only deletion vectors for deletes.",
stagingSnapshot.snapshotId(), stagingBranch, deleteFile.location()));
}
}
return new StagingInputs(newDataFiles, stagingDVFiles, eqDeleteFiles);
}
private void validateDeleteSpecPartitionColumns(Snapshot stagingSnapshot, DeleteFile deleteFile) {
PartitionSpec spec = table.specs().get(deleteFile.specId());
for (PartitionField field : spec.fields()) {
Preconditions.checkState(
eqFieldIds.contains(field.sourceId()),
"Staging snapshot %s on branch '%s' contains an equality delete file %s under spec %s, "
+ "which partitions by field '%s' (source id %s) that is not an equality field %s. "View on GitHub (pinned to 86d9c8fc54)
Solutions
- Upgrade the table to format version 3 before running the conversion: ALTER TABLE ... SET TBLPROPERTIES ('format-version'='3').
- Ensure all writers (all Flink jobs, Spark, Trino, etc.) use V3-enabled Iceberg versions that produce DVs, then run compaction/rewrite to convert existing positional deletes to DVs.
- Point the staging branch at a snapshot produced exclusively by the V3-enabled Flink writer.
Example fix
// before
spark.sql("ALTER TABLE db.t SET TBLPROPERTIES ('format-version'='2')");
runEqualityConvert(table);
// after
spark.sql("ALTER TABLE db.t SET TBLPROPERTIES ('format-version'='3')");
spark.sql("CALL catalog.system.rewrite_data_files(table => 'db.t')"); // clear positional deletes
runEqualityConvert(table); Defensive patterns
Strategy: validation
Validate before calling
if (!"3".equals(table.properties().getOrDefault(TableProperties.FORMAT_VERSION, "2"))) {
throw new IllegalStateException("Equality delete conversion requires format-version 3; upgrade the table first");
} Try / catch
try {
runEqualityConvertJob(table, cfg);
} catch (IllegalStateException e) {
if (e.getMessage().contains("positional delete file")) {
LOG.error("Staging branch is not V3/DV-only: upgrade format version and rewrite deletes", e);
} else throw e;
} Prevention
- Upgrade the table to format-version 3 before enabling DV conversion
- Ensure every writer engine (Spark, Flink, Trino) runs a DV-capable Iceberg version
- Rewrite existing positional deletes via compaction after upgrading
- Point staging at a branch written exclusively by the V3 Flink writer
When it happens
Trigger: The table is still format-version 2 (or the staging branch snapshot contains files written under V2), so deletes are stored as positional delete files instead of DVs; a scan of the staging snapshot yields a DeleteFile that is not recognized by ContentFileUtil.isDV.
Common situations: Forgetting to upgrade the table to format version 3 (ALTER TABLE ... SET TBLPROPERTIES ('format-version'='3')) before enabling equality-delete conversion; older writers or other engines writing V2 positional deletes to the staging branch; mixed-version clusters writing to the same table.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- V2 positional delete file %s attached to main data file %s;
- V2 positional delete file %s attached to main data file %s;
- Staging snapshot %s on branch '%s' contains a V2 positional
- 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/b971c8f64ad77b65.
Report an issue: GitHub.