{"record":{"id":"1b47bb4fbf6e8866","repo":"apache/iceberg","slug":"unsorted-order-id-must-be-0-1b47bb","errorCode":null,"errorMessage":"Unsorted order ID must be 0","messagePattern":"Unsorted order ID must be 0","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"api/src/main/java/org/apache/iceberg/UnboundSortOrder.java","lineNumber":109,"sourceCode":"    private Integer orderId = null;\n\n    private Builder() {}\n\n    Builder withOrderId(int newOrderId) {\n      this.orderId = newOrderId;\n      return this;\n    }\n\n    Builder addSortField(\n        String transformAsString, int sourceId, SortDirection direction, NullOrder nullOrder) {\n      fields.add(new UnboundSortField(transformAsString, sourceId, direction, nullOrder));\n      return this;\n    }\n\n    UnboundSortOrder build() {\n      if (fields.isEmpty()) {\n        if (orderId != null && orderId != 0) {\n          throw new IllegalArgumentException(\"Unsorted order ID must be 0\");\n        }\n        return UNSORTED_ORDER;\n      }\n\n      if (orderId != null && orderId == 0) {\n        throw new IllegalArgumentException(\"Sort order ID 0 is reserved for unsorted order\");\n      }\n\n      // default ID to 1 as 0 is reserved for unsorted order\n      int actualOrderId = orderId != null ? orderId : 1;\n      return new UnboundSortOrder(actualOrderId, fields);\n    }\n  }\n\n  static class UnboundSortField {\n    private final Transform<?, ?> transform;\n    private final int sourceId;\n    private final SortDirection direction;","sourceCodeStart":91,"sourceCodeEnd":127,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/api/src/main/java/org/apache/iceberg/UnboundSortOrder.java#L91-L127","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Use order ID 0 for an unsorted sort order.","Omit the order ID (leave it null) and let the builder return the UNSORTED_ORDER singleton.","Only assign a non-zero ID when the sort order actually has sort fields."],"exampleFix":"// before\nUnboundSortOrder order = SortOrder.builderFor(schema)\n    .withOrderId(3)\n    .build(); // no fields -> throws\n\n// after\nUnboundSortOrder order = SortOrder.builderFor(schema)\n    .withOrderId(0)\n    .build(); // unsorted","handlingStrategy":"validation","validationCode":"if (sortFields.isEmpty() && orderId != null && orderId != 0) {\n  throw new IllegalArgumentException(\"Unsorted sort order must use ID 0\");\n}","typeGuard":"boolean isValidUnsorted = sortFields.isEmpty() ? (orderId == null || orderId == 0) : (orderId == null || orderId != 0);","tryCatchPattern":"try {\n  return builder.build();\n} catch (IllegalArgumentException e) {\n  if (e.getMessage().contains(\"Unsorted order ID\")) {\n    return builder.withOrderId(0).build();\n  }\n  throw e;\n}","preventionTips":["Reserve order ID 0 for the unsorted order.","Default order IDs: unsorted = 0, sorted starts at 1.","Validate order ID vs. field count before building."],"tags":["sort-order","illegal-argument","validation","schema"],"backgroundTag":"invalid-argument-value","analyzedSha":"86d9c8fc543e7c56c9f624eb725f76c9baff9570","analyzedAt":"2026-09-12T00:46:39.097Z","contentChangedAt":"2026-09-12T00:46:39.097Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}