{"record":{"id":"4bad2a0fc22d8af5","repo":"apache/iceberg","slug":"sort-order-id-0-is-reserved-for-unsorted-order-4bad2a","errorCode":null,"errorMessage":"Sort order ID 0 is reserved for unsorted order","messagePattern":"Sort order ID 0 is reserved for unsorted order","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"api/src/main/java/org/apache/iceberg/UnboundSortOrder.java","lineNumber":115,"sourceCode":"      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;\n    private final NullOrder nullOrder;\n\n    private UnboundSortField(\n        String transformAsString, int sourceId, SortDirection direction, NullOrder nullOrder) {\n      this.transform = Transforms.fromString(transformAsString);\n      this.sourceId = sourceId;","sourceCodeStart":97,"sourceCodeEnd":133,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/api/src/main/java/org/apache/iceberg/UnboundSortOrder.java#L97-L133","documentation":"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).","triggerScenarios":"Building an UnboundSortOrder with one or more sort fields while explicitly calling withOrderId(0), e.g. SortOrder.builderFor(schema).asc(\"id\").withOrderId(0).build().","commonSituations":"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.","solutions":["Assign an order ID of 1 or greater when sort fields are present.","Omit the order ID so it defaults to 1.","Reserve ID 0 only for the unsorted order."],"exampleFix":"// before\nSortOrder.builderFor(schema).asc(\"id\").withOrderId(0).build();\n\n// after\nSortOrder.builderFor(schema).asc(\"id\").withOrderId(1).build();","handlingStrategy":"validation","validationCode":"if (!sortFields.isEmpty() && orderId != null && orderId == 0) {\n  throw new IllegalArgumentException(\"Sort order ID 0 is reserved for unsorted\");\n}","typeGuard":"boolean isValidSorted = sortFields.isEmpty() || orderId == null || orderId > 0;","tryCatchPattern":"try {\n  return builder.build();\n} catch (IllegalArgumentException e) {\n  if (e.getMessage().contains(\"reserved for unsorted\")) {\n    return builder.build(); // let ID default to 1\n  }\n  throw e;\n}","preventionTips":["Never hardcode order ID 0 for sorted orders.","Omit the order ID to let it default to 1.","Remember: 0 = unsorted, 1+ = sorted in the Iceberg spec."],"tags":["sort-order","illegal-argument","validation","reserved-identifier"],"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"}