apache/iceberg · error · UnsupportedOperationException

Cannot convert unknown expression: ${expr}

Error message

Cannot convert unknown expression: ${expr}

What it means

toIcebergTerm only understands Transform and NamedReference Spark expressions. Any other expression tree node passed in (e.g. a literal, binary comparison, or function call) cannot be converted to an Iceberg term, so this UnsupportedOperationException is thrown.

Source

Thrown at spark/v3.5/spark/src/main/java/org/apache/iceberg/spark/Spark3Util.java:412

          return org.apache.iceberg.expressions.Expressions.hour(colName);
        case "truncate":
          return org.apache.iceberg.expressions.Expressions.truncate(colName, findWidth(transform));
        case "zorder":
          return new Zorder(
              Stream.of(transform.references())
                  .map(ref -> DOT.join(ref.fieldNames()))
                  .map(org.apache.iceberg.expressions.Expressions::ref)
                  .collect(Collectors.toList()));
        default:
          throw new UnsupportedOperationException("Transform is not supported: " + transform);
      }

    } else if (expr instanceof NamedReference) {
      NamedReference ref = (NamedReference) expr;
      return org.apache.iceberg.expressions.Expressions.ref(DOT.join(ref.fieldNames()));

    } else {
      throw new UnsupportedOperationException("Cannot convert unknown expression: " + expr);
    }
  }

  /**
   * Converts Spark transforms into a {@link PartitionSpec}.
   *
   * @param schema the table schema
   * @param partitioning Spark Transforms
   * @return a PartitionSpec
   */
  public static PartitionSpec toPartitionSpec(Schema schema, Transform[] partitioning) {
    if (partitioning == null || partitioning.length == 0) {
      return PartitionSpec.unpartitioned();
    }

    PartitionSpec.Builder builder = PartitionSpec.builderFor(schema);
    for (Transform transform : partitioning) {
      Preconditions.checkArgument(

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. Use only plain columns or supported transforms in PARTITIONED BY / sort clauses
  2. Add a computed column first, then partition on it
  3. Construct the term programmatically with Expressions.ref/bucket/truncate instead of going through the Spark converter

Example fix

// before
PARTITIONED BY (bucket(16, id), ts + INTERVAL 1 DAY)
// after
PARTITIONED BY (bucket(16, id), days(ts))
Defensive patterns

Strategy: type-guard

Validate before calling

if (!(expr instanceof Transform) && !(expr instanceof NamedReference)) { throw new IllegalArgumentException("Expression must be a Transform or NamedReference"); }

Type guard

boolean convertibleToIcebergTerm(Expression e) { return e instanceof Transform || e instanceof NamedReference; }

Try / catch

try { Spark3Util.toIcebergTerm(expr); } catch (UnsupportedOperationException e) { if (e.getMessage().contains("Cannot convert unknown expression")) { /* rebuild the expression as a transform/ref */ } else throw e; }

Prevention

When it happens

Trigger: Passing a non-transform, non-column expression into partition/sort conversion paths — e.g. IcebergPartitioningHelpers or CREATE TABLE ... PARTITIONED BY with a predicate or computed expression rather than a plain column or supported transform.

Common situations: PARTITIONED BY clauses containing arithmetic or function calls (PARTITIONED BY (year(col) + 1)); programmatic construction of V2 expressions that aren't NamedReference/Transform.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


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