apache/iceberg · error · UnsupportedOperationException
Cannot split a task which is already split
Error message
Cannot split a task which is already split
What it means
SplitScanTask (and CombinedScanTask wrappers) represent a task that has already been divided by split size, so calling split() on it again is meaningless. Iceberg throws UnsupportedOperationException to enforce the one-level-split invariant in scan planning. Once split, a task can only be merged with others or executed.
Source
Thrown at core/src/main/java/org/apache/iceberg/BaseFileScanTask.java:163
@Override
public long sizeBytes() {
return len + deletesSizeBytes();
}
@Override
public int filesCount() {
return fileScanTask.filesCount();
}
@Override
public Expression residual() {
return fileScanTask.residual();
}
@Override
public Iterable<FileScanTask> split(long splitSize) {
throw new UnsupportedOperationException("Cannot split a task which is already split");
}
@Override
public boolean canMerge(ScanTask other) {
if (other instanceof SplitScanTask) {
SplitScanTask that = (SplitScanTask) other;
return file().equals(that.file()) && offset + len == that.start();
} else {
return false;
}
}
@Override
public SplitScanTask merge(ScanTask other) {
SplitScanTask that = (SplitScanTask) other;
// don't use deletesSizeBytes() here so that deletesSizeBytes is only calculated once after
// merging rather than for each task before merging
return new SplitScanTask(offset, len + that.length(), fileScanTask, deletesSizeBytes);View on GitHub (pinned to 86d9c8fc54)
Solutions
- Check task type before splitting: only call split on base file/scan tasks, not SplitScanTask/CombinedScanTask
- Use the task's existing length() to distribute work instead of re-splitting
- If merging is needed instead, use canMerge/merge or wrap tasks via CombinedScanTask
Example fix
// before
for (FileScanTask task : scan.planFiles()) {
tasks.addAll(task.split(targetSize)); // throws for already-split tasks
}
// after
for (FileScanTask task : scan.planFiles()) {
if (!(task instanceof SplitScanTask)) {
tasks.addAll(task.split(targetSize));
} else {
tasks.add(task);
}
} Defensive patterns
Strategy: type-guard
Validate before calling
boolean splittable = !(task instanceof SplitScanTask) && !(task instanceof CombinedScanTask);
Type guard
if (task instanceof SplitScanTask || task instanceof CombinedScanTask) { /* already split/combined: use as-is */ } else { task.split(splitSize); } Try / catch
try { result = task.split(splitSize); } catch (UnsupportedOperationException e) { result = Collections.singletonList(task); } Prevention
- Treat split() results as terminal; never call split on planned task output
- Check instanceof before splitting tasks from heterogeneous sources
- Handle CombinedScanTask and SplitScanTask explicitly in task-distribution code
When it happens
Trigger: Calling split(splitSize) on a FileScanTask returned by another split() call, e.g. re-splitting results from TableScan.planTasks()/planFiles() that produced SplitScanTask instances.
Common situations: Custom scan-planning or task-distribution code that iterates planned tasks and calls split() defensively; frameworks that re-partition tasks across executors after planning.
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
- Unsupported task group for row-based reads: ${partition.task
- Unsupported task group for row-based reads: ${partition.task
- %s doesn't implement copyWithStats
- %s doesn't implement validateFilesExist
- Can't retrieve values from an empty struct
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/3b17a210d528ef0f.
Report an issue: GitHub.