apache/beam · error · IllegalArgumentException

Could not parse sort modifiers

Error message

Could not parse sort modifiers '{}' in '{}'. Expected: [asc|desc] [nulls first|nulls last].

What it means

SortOrderUtils.parse builds an Iceberg SortOrder from a string spec like 'id asc nulls last'. When the modifier suffix of a field expression (e.g. 'desc nulls first') fails the MODIFIERS regex, it throws IllegalArgumentException with this message, echoing the bad modifiers and the full field expression.

Solutions

  1. Use only the accepted tokens: field name optionally followed by 'asc' or 'desc', optionally followed by 'nulls first' or 'nulls last' (e.g. 'id desc nulls last')
  2. Remove extra words/commas so each field spec is a single expression ('id asc', not 'ORDER BY id asc')
  3. Check exact spelling and casing expected by SortOrderUtils, and trim stray whitespace before parsing

Example fix

// before
String spec = "id DESC NULLS FIRST"; // rejected by MODIFIERS regex
// after
String spec = "id desc nulls first";
Defensive patterns

Strategy: validation

Validate before calling

private static final java.util.regex.Pattern MODIFIERS_OK =
    java.util.regex.Pattern.compile("(asc|desc)?(\\s+nulls\\s+(first|last))?\\s*");

void checkSortField(String field) {
  String[] parts = field.trim().split("\\s+", 2);
  String rest = parts.length > 1 ? parts[1] : "";
  if (!rest.isEmpty() && !MODIFIERS_OK.matcher(rest).matches()) {
    throw new IllegalArgumentException("Bad sort modifiers in: " + field);
  }
}

Type guard

boolean isValidSortField(String field) {
  return field != null && field.matches("\\w+\\s*(asc|desc)?(\\s+nulls\\s+(first|last))?\\s*");
}

Try / catch

try {
  SortOrder order = SortOrderUtils.parsed(spec);
} catch (IllegalArgumentException e) {
  // log e.getMessage(), fix config or fall back to default sort order
}

Prevention

When it happens

Trigger: Passing a sort field string whose direction/null-order suffix doesn't match `[asc|desc] [nulls first|nulls last]` — e.g. 'name ascending', 'id DESC NULLS FIRST' with case handling the regex rejects, extra tokens ('id desc nulls first extra'), or misspellings ('nuls last').

Common situations: Configuring IcebergIO sort orders via pipeline options/CLI where whitespace or typos slip in; copy-pasted SQL-style ORDER BY clauses ('ORDER BY' keyword, comma handling) pasted into the spec string; locale or case differences.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of apache/beam@12126d8942 (2026-09-13). Data as JSON: /api/errors/94009b2831828168. Report an issue: GitHub.

Appendix: source

Thrown at sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/SortOrderUtils.java:83

      }
    }
    return builder.build();
  }

  private static ParsedSortField parse(String field) {
    field = field.trim();
    int splitAt = findTopLevelWhitespace(field);
    String termStr = (splitAt < 0 ? field : field.substring(0, splitAt)).trim();
    String rest = (splitAt < 0 ? "" : field.substring(splitAt)).trim();

    Term term = PartitionUtils.toIcebergTerm(termStr);
    boolean ascending = true;
    @Nullable NullOrder nullOrder = null;

    if (!rest.isEmpty()) {
      Matcher matcher = MODIFIERS.matcher(rest);
      if (!matcher.matches()) {
        throw new IllegalArgumentException(
            "Could not parse sort modifiers '"
                + rest
                + "' in '"
                + field
                + "'. Expected: [asc|desc] [nulls first|nulls last].");
      }
      String dir = matcher.group("dir");
      if (dir != null) {
        ascending = dir.toLowerCase(Locale.ROOT).equals("asc");
      }
      String nulls = matcher.group("nulls");
      if (nulls != null) {
        nullOrder =
            nulls.toLowerCase(Locale.ROOT).equals("first")
                ? NullOrder.NULLS_FIRST
                : NullOrder.NULLS_LAST;
      }
    }

View on GitHub (pinned to 12126d8942)