apache/iceberg · error · IllegalArgumentException

Sort order ID 0 is reserved for unsorted order

Error message

Sort order ID 0 is reserved for unsorted order

What it means

UnboundSortOrder.build() rejects sort order ID 0 for any sort order that has fields, because the Iceberg spec reserves ID 0 exclusively for the unsorted order. A sort order with fields must be assigned ID 1 or higher (or left null, defaulting to 1).

Source

Thrown at api/src/main/java/org/apache/iceberg/UnboundSortOrder.java:115

      return this;
    }

    Builder addSortField(
        String transformAsString, int sourceId, SortDirection direction, NullOrder nullOrder) {
      fields.add(new UnboundSortField(transformAsString, sourceId, direction, nullOrder));
      return this;
    }

    UnboundSortOrder build() {
      if (fields.isEmpty()) {
        if (orderId != null && orderId != 0) {
          throw new IllegalArgumentException("Unsorted order ID must be 0");
        }
        return UNSORTED_ORDER;
      }

      if (orderId != null && orderId == 0) {
        throw new IllegalArgumentException("Sort order ID 0 is reserved for unsorted order");
      }

      // default ID to 1 as 0 is reserved for unsorted order
      int actualOrderId = orderId != null ? orderId : 1;
      return new UnboundSortOrder(actualOrderId, fields);
    }
  }

  static class UnboundSortField {
    private final Transform<?, ?> transform;
    private final int sourceId;
    private final SortDirection direction;
    private final NullOrder nullOrder;

    private UnboundSortField(
        String transformAsString, int sourceId, SortDirection direction, NullOrder nullOrder) {
      this.transform = Transforms.fromString(transformAsString);
      this.sourceId = sourceId;

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. Assign an order ID of 1 or greater when sort fields are present.
  2. Omit the order ID so it defaults to 1.
  3. Reserve ID 0 only for the unsorted order.

Example fix

// before
SortOrder.builderFor(schema).asc("id").withOrderId(0).build();

// after
SortOrder.builderFor(schema).asc("id").withOrderId(1).build();
Defensive patterns

Strategy: validation

Validate before calling

if (!sortFields.isEmpty() && orderId != null && orderId == 0) {
  throw new IllegalArgumentException("Sort order ID 0 is reserved for unsorted");
}

Type guard

boolean isValidSorted = sortFields.isEmpty() || orderId == null || orderId > 0;

Try / catch

try {
  return builder.build();
} catch (IllegalArgumentException e) {
  if (e.getMessage().contains("reserved for unsorted")) {
    return builder.build(); // let ID default to 1
  }
  throw e;
}

Prevention

When it happens

Trigger: Building an UnboundSortOrder with one or more sort fields while explicitly calling withOrderId(0), e.g. SortOrder.builderFor(schema).asc("id").withOrderId(0).build().

Common situations: Hand-assigning order IDs when creating new sort orders; copying ID 0 from an unsorted table's metadata into a new sorted order; off-by-one thinking that IDs start at 0.

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


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