apache/iceberg · error · IllegalArgumentException

Unsorted order ID must be 0

Error message

Unsorted order ID must be 0

What it means

UnboundSortOrder.build() validates that an empty (unsorted) sort order must use order ID 0, because ID 0 is conventionally reserved for the unsorted order in the Iceberg spec. Passing a non-zero order ID with no sort fields is contradictory, so it throws IllegalArgumentException.

Source

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

    private Integer orderId = null;

    private Builder() {}

    Builder withOrderId(int newOrderId) {
      this.orderId = newOrderId;
      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;

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. Use order ID 0 for an unsorted sort order.
  2. Omit the order ID (leave it null) and let the builder return the UNSORTED_ORDER singleton.
  3. Only assign a non-zero ID when the sort order actually has sort fields.

Example fix

// before
UnboundSortOrder order = SortOrder.builderFor(schema)
    .withOrderId(3)
    .build(); // no fields -> throws

// after
UnboundSortOrder order = SortOrder.builderFor(schema)
    .withOrderId(0)
    .build(); // unsorted
Defensive patterns

Strategy: validation

Validate before calling

if (sortFields.isEmpty() && orderId != null && orderId != 0) {
  throw new IllegalArgumentException("Unsorted sort order must use ID 0");
}

Type guard

boolean isValidUnsorted = sortFields.isEmpty() ? (orderId == null || orderId == 0) : (orderId == null || orderId != 0);

Try / catch

try {
  return builder.build();
} catch (IllegalArgumentException e) {
  if (e.getMessage().contains("Unsorted order ID")) {
    return builder.withOrderId(0).build();
  }
  throw e;
}

Prevention

When it happens

Trigger: Building an UnboundSortOrder with zero fields (unsorted) while explicitly setting orderId to any value other than 0, e.g. SortOrder.builderFor(schema).withOrderId(5).build() with no sort fields added.

Common situations: Constructing sort orders programmatically when copying order IDs from table metadata; misinterpreting order IDs when deserializing or rebuilding sort orders; assigning fresh IDs to an intentionally unsorted order.

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/1b47bb4fbf6e8866. Report an issue: GitHub.