apache/iceberg · error · IllegalArgumentException
Cannot delete map keys:
Error message
Cannot delete map keys:
What it means
Iceberg map types store keys as a hidden nested field, and the schema evolution spec forbids any change to map keys because keys are part of the data's identity/partitioning semantics. When SchemaUpdate.apply detects that the map's key field ID appears in the deletes set, it throws this IllegalArgumentException rather than producing an invalid updated schema.
Solutions
- Do not delete map keys; instead delete the whole map column by its top-level field ID
- Restrict delete operations to fields surfaced as user-manageable (top-level columns and struct fields)
Example fix
// before updateSchema.delete(mapType.fields().get(0).fieldId()); // key id — throws // after updateSchema.delete(mapColumn.fieldId()); // delete the map column instead
Defensive patterns
Strategy: validation
Validate before calling
Types.MapType map = ...;
int keyId = map.fields().get(0).fieldId();
if (requestedDeletes.contains(keyId)) throw new IllegalArgumentException("Map keys cannot be deleted; delete the map column " + mapColumnId + " instead"); Type guard
static boolean isMapKeyId(Types.NestedField mapColumn) {
return mapColumn.type() instanceof Types.MapType && mapColumn.type().asMapType().fields().get(0).fieldId() != mapColumn.fieldId();
}
// guard: requestedDeleteId != mapType.asMapType().keyId() Try / catch
try {
updateSchema.apply();
} catch (IllegalArgumentException e) {
if (e.getMessage().equals("Cannot delete map keys: " + e.getMessage().substring("Cannot delete map keys: ".length()))) { /* always rebuild message check */ }
if (e.getMessage().startsWith("Cannot delete map keys")) throw new IllegalStateException("Drop and re-add the map column instead", e);
throw e;
} Prevention
- Never target map key field IDs with delete operations
- Only expose top-level columns and struct children as deletable in tooling UIs
- Remember map keys are identity-bearing and immutable by spec
When it happens
Trigger: Calling updateSchema.delete(keyFieldId) where keyFieldId is the internal field ID of a map's key (map.fields().get(0).fieldId()).
Common situations: Generic schema-cleanup tooling iterating over all nested field IDs (including implicit map key/value IDs) and deleting them; users misreading the map key's field ID from Schema.description/ids and targeting it for removal.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- Cannot add fields to map keys:
- Cannot alter map keys:
- Cannot delete element type from list:
- Cannot update map keys:
- Cannot delete value type from map:
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/ee4f81e0852cd6cf.
Report an issue: GitHub.
Appendix: source
Thrown at core/src/main/java/org/apache/iceberg/SchemaUpdate.java:718
elementUpdate != null ? elementUpdate.isOptional() : list.isElementOptional();
if (isElementOptional == elementField.isOptional() && list.elementType() == elementType) {
return list;
}
if (isElementOptional) {
return Types.ListType.ofOptional(list.elementId(), elementType);
} else {
return Types.ListType.ofRequired(list.elementId(), elementType);
}
}
@Override
public Type map(Types.MapType map, Type kResult, Type valueResult) {
// if any updates are intended for the key, throw an exception
int keyId = map.fields().get(0).fieldId();
if (deletes.contains(keyId)) {
throw new IllegalArgumentException("Cannot delete map keys: " + map);
} else if (updates.containsKey(keyId)) {
throw new IllegalArgumentException("Cannot update map keys: " + map);
} else if (parentToAddedIds.containsKey(keyId)) {
throw new IllegalArgumentException("Cannot add fields to map keys: " + map);
} else if (!map.keyType().equals(kResult)) {
throw new IllegalArgumentException("Cannot alter map keys: " + map);
}
// use field to apply updates to the value
Types.NestedField valueField = map.fields().get(1);
Type valueType = field(valueField, valueResult);
if (valueType == null) {
throw new IllegalArgumentException("Cannot delete value type from map: " + map);
}
Types.NestedField valueUpdate = updates.get(valueField.fieldId());
boolean isValueOptional =
valueUpdate != null ? valueUpdate.isOptional() : map.isValueOptional();View on GitHub (pinned to 86d9c8fc54)