apache/iceberg · error · UnsupportedOperationException

Expected value to be date or timestamp: ${valueType.catalogS

Error message

Expected value to be date or timestamp: ${valueType.catalogString()}

What it means

YearsFunction is a Spark Iceberg catalog function (years()) that only accepts DATE or TIMESTAMP/TIMESTAMP_NTZ input. During binding, the given input type is checked; any other type (e.g. numeric or string) is rejected because converting an arbitrary value to a year-partition value is undefined. The type's catalogString is appended to help identify the offending type.

Source

Thrown at spark/v4.0/spark/src/main/java/org/apache/iceberg/spark/functions/YearsFunction.java:46

import org.apache.spark.sql.types.TimestampType;

/**
 * A Spark function implementation for the Iceberg year transform.
 *
 * <p>Example usage: {@code SELECT system.years('source_col')}.
 */
public class YearsFunction extends UnaryUnboundFunction {

  @Override
  protected BoundFunction doBind(DataType valueType) {
    if (valueType instanceof DateType) {
      return new DateToYearsFunction();
    } else if (valueType instanceof TimestampType) {
      return new TimestampToYearsFunction();
    } else if (valueType instanceof TimestampNTZType) {
      return new TimestampNtzToYearsFunction();
    } else {
      throw new UnsupportedOperationException(
          "Expected value to be date or timestamp: " + valueType.catalogString());
    }
  }

  @Override
  public String description() {
    return name()
        + "(col) - Call Iceberg's year transform\n"
        + "  col :: source column (must be date or timestamp)";
  }

  @Override
  public String name() {
    return "years";
  }

  private abstract static class BaseToYearsFunction extends BaseScalarFunction<Integer> {
    @Override

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. Cast the column to date or timestamp before applying years(): years(cast(epoch_col as timestamp)).
  2. Parse string columns with to_date()/to_timestamp() before passing to years().
  3. Verify the column's actual type with DESCRIBE TABLE and fix the ingestion schema.
  4. If you just want the calendar year as an int, use Spark's built-in year(col) function instead of the Iceberg years() transform.

Example fix

// before
SELECT years(epoch_millis) FROM t;
// after
SELECT years(cast(epoch_millis / 1000 as timestamp)) FROM t;
Defensive patterns

Strategy: validation

Validate before calling

if (!(col.dataType() instanceof DateType) && !(col.dataType() instanceof TimestampType) && !(col.dataType() instanceof TimestampNTZType)) { throw new IllegalArgumentException("years() requires date/timestamp: " + col.dataType().catalogString()); }

Type guard

boolean isTemporal(DataType t) { return t instanceof DateType || t instanceof TimestampType || t instanceof TimestampNTZType; }

Prevention

When it happens

Trigger: Calling SELECT years(col) in Spark SQL (or referencing the years() function) where col is not a date or timestamp — e.g. a bigint epoch column, a string 'yyyy-MM-dd', or an int year.

Common situations: Passing an epoch-millis BIGINT column to years() instead of casting to timestamp first; a table column typed as string because source data was ingested untyped; confusion with Spark's built-in year() which accepts date/timestamp only anyway.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


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