apache/iceberg · error · UnsupportedOperationException
does not implement markRowDeleted
Error message
does not implement markRowDeleted
What it means
DeleteFilter.markRowDeleted is an extension hook that subclasses override to mark a record as deleted when applying equality deletes in-place. The base implementation throws UnsupportedOperationException, so calling it on a DeleteFilter subclass that did not override it (or a caller invoking the base method directly) fails.
Source
Thrown at data/src/main/java/org/apache/iceberg/data/DeleteFilter.java:245
public CloseableIterable<T> findEqualityDeleteRows(CloseableIterable<T> records) {
// Predicate to test whether a row has been deleted by equality deletions.
Predicate<T> deletedRows = applyEqDeletes().stream().reduce(Predicate::or).orElse(t -> false);
return CloseableIterable.filter(records, deletedRows);
}
private CloseableIterable<T> applyEqDeletes(CloseableIterable<T> records) {
if (eqDeletes.isEmpty()) {
return records;
}
Predicate<T> isEqDeleted = applyEqDeletes().stream().reduce(Predicate::or).orElse(t -> false);
return createDeleteIterable(records, isEqDeleted);
}
protected void markRowDeleted(T item) {
throw new UnsupportedOperationException(
this.getClass().getName() + " does not implement markRowDeleted");
}
public Predicate<T> eqDeletedRowFilter() {
if (eqDeleteRows == null) {
eqDeleteRows =
applyEqDeletes().stream().map(Predicate::negate).reduce(Predicate::and).orElse(t -> true);
}
return eqDeleteRows;
}
public PositionDeleteIndex deletedRowPositions() {
if (deleteRowPositions == null && !posDeletes.isEmpty()) {
this.deleteRowPositions = deleteLoader().loadPositionDeletes(posDeletes, filePath);
}
return deleteRowPositions;
}View on GitHub (pinned to 86d9c8fc54)
Solutions
- Override markRowDeleted in your DeleteFilter subclass to implement the row-mutation semantics.
- Use eqDeletedRowFilter()/applyEqDeletes() (predicate-based filtering) instead of the mutation path if you don't mutate rows in place.
- Check which code path is calling markRowDeleted and switch to one your subclass supports.
- Fix subclass inheritance so the concrete filter used at runtime actually implements the hook.
Example fix
// before
public class MyFilter extends DeleteFilter<Record> { /* no markRowDeleted */ }
// after
@Override
protected void markRowDeleted(Record item) {
item.setField("_deleted", true);
} Defensive patterns
Strategy: validation
Validate before calling
if (filter.getClass().getMethod("markRowDeleted")
.getDeclaringClass() == DeleteFilter.class) {
throw new IllegalStateException("Filter does not support in-place row deletion");
} Type guard
boolean supportsMarkRowDeleted(DeleteFilter<?> f) {
try { f.getClass().getDeclaredMethod("markRowDeleted", Object.class); return true; }
catch (NoSuchMethodException e) { return false; }
} Try / catch
try {
filter.markRowDeleted(item);
} catch (UnsupportedOperationException e) {
// fall back to predicate-based filtering
Predicate<T> isDeleted = filter.eqDeletedRowFilter();
} Prevention
- When subclassing DeleteFilter, override all hooks your code path relies on (markRowDeleted or eqDeletedRowFilter).
- Add an abstract smoke test for custom filters that exercises both delete application paths.
- Document which base methods are abstract-by-contract despite having default throwing implementations.
When it happens
Trigger: Calling markRowDeleted on a DeleteFilter instance whose concrete class does not override the method — e.g. a custom filter subclass that only implemented applyEqDeletes/eqDeletedRowFilter but uses a code path (like applyEqDeletes in mutation mode) that relies on markRowDeleted.
Common situations: Custom engine integrations extending DeleteFilter incompletely; refactors that changed which hook is used (filter-based vs mutation-based equality delete application); invoking protected hooks reflectively or from new code paths.
Related errors
- Delete files are currently not supported in changelog scans
- Unsupported delete granularity: <granularity>
- Unknown delete file content:
- Cannot read data task.
- %s doesn't implement copyWithStats
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/573e1aa17fdb504f.
Report an issue: GitHub.