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

  1. Check task type before splitting: only call split on base file/scan tasks, not SplitScanTask/CombinedScanTask
  2. Use the task's existing length() to distribute work instead of re-splitting
  3. 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

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


AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12). Data as JSON: /api/errors/3b17a210d528ef0f. Report an issue: GitHub.