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
While collecting existing deletes attached to a main-branch data file, the loader found a V2 positional delete file. The converter requires the target to be format version 3 with deletes represented only as deletion vectors, so encountering a positional delete means the table state is incompatible with the conversion and the operator fails fast.
Source
Thrown at flink/v1.20/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
- Upgrade the target table to format-version 3 so all subsequent deletes become DVs.
- Find which writer/job committed positional deletes on the target branch and switch it to a V3 DV writer.
- Remove or convert legacy positional delete files (e.g. rewrite/compact the affected data files) before running conversion.
- Run the converter only on branches whose delete files are DVs (validated via ContentFileUtil.isDV).
Example fix
// before: Spark job writing to main at format-version 2
spark.sql("DELETE FROM t WHERE id = 42"); // emits positional deletes
// after: migrate table to V3 so deletes are DVs
spark.sql("ALTER TABLE t SET TBLPROPERTIES ('format-version'='3')"); Defensive patterns
Strategy: validation
Validate before calling
// before conversion, ensure target branch has no positional deletes
table.snapshot(targetBranch).deleteFiles(table.io()).forEach(f ->
checkState(f.content() != FileContent.POSITION_DELETES,
"positional delete on target: " + f.location())); Type guard
boolean targetHasOnlyDvs(Table table, String branch) {
return table.snapshot(branch).deleteFiles(table.io()).stream()
.noneMatch(f -> f.content() == FileContent.POSITION_DELETES);
} Try / catch
try {
convert(table);
} catch (IllegalStateException e) {
if (e.getMessage().startsWith("V2 positional delete file")) {
compactAffectedDataFiles(); // removes positional deletes, then rerun
} else throw e;
} Prevention
- Migrate the table to format-version 3 before converting.
- Ensure every writer job targets V3 DV delete mode.
- Exclude legacy V2 delete files via compaction before running conversion.
- Add a pre-flight delete-file content audit to the maintenance pipeline.
When it happens
Trigger: EqualityConvertReader.loadExistingDVs() iterates task.deletes() for a main data file and hits a DeleteFile with content POSITION_DELETES - i.e. some writer produced classic positional deletes on the target branch.
Common situations: Target table at format-version 2; Spark/Flink writers without DV support committing to the target branch; stale engine writing to a table that was migrated to V3 but whose branch still has old delete files.
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
- Staging snapshot %s on branch '%s' contains a V2 positional
- 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;
- Staging snapshot %s on branch '%s' contains a V2 positional
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/b1dd3910117979f1.
Report an issue: GitHub.