apache/iceberg · error · IllegalStateException

Unknown type for timestamp_ns: " + timeVal.getClass()

Error message

Unknown type for timestamp_ns: " + timeVal.getClass()

What it means

For nanosecond-precision timestamps (precision > 6), StructRowData.getTimestamp expects the wrapped StructLike to hold OffsetDateTime, LocalDateTime, or Long (epoch nanos). Encountering any other runtime type means the stored value cannot represent a timestamp_ns field, and IllegalStateException is thrown naming the offending class.

Solutions

  1. Convert the stored value (see the class name in the message) to OffsetDateTime, LocalDateTime, or epoch-nanos Long before it reaches StructRowData.
  2. Confirm the column precision in the read schema matches the physical data; reading nanos data with micros assumptions can feed wrong values.
  3. If using a custom writer, store timestamp_ns as Long epoch-nanos, LocalDateTime, or OffsetDateTime only.
  4. For Instant values, convert with odt = instant.atOffset(ZoneOffset.UTC) before wrapping.

Example fix

// before
TimestampData ts = structRowData.getTimestamp(pos, 9); // fails for Instant

// after
Object raw = struct.get(pos, Object.class);
OffsetDateTime normalized = (raw instanceof Instant i)
    ? i.atOffset(ZoneOffset.UTC)
    : (OffsetDateTime) raw;
TimestampData ts = TimestampData.fromEpochMillis(
    normalized.toInstant().toEpochMilli(), normalized.getNano() % 1_000_000);
Defensive patterns

Strategy: validation

Validate before calling

Object raw = struct.get(pos, Object.class);
Preconditions.checkArgument(
    precision <= 6 || raw == null || raw instanceof OffsetDateTime
        || raw instanceof LocalDateTime || raw instanceof Long,
    "timestamp_ns field holds unexpected type: %s", raw == null ? "null" : raw.getClass());

Type guard

boolean isNanosTimestampRepresentable(Object v) {
  return v instanceof OffsetDateTime || v instanceof LocalDateTime || v instanceof Long;
}

Try / catch

try {
  TimestampData ts = structRowData.getTimestamp(pos, 9);
} catch (IllegalStateException e) {
  if (e.getMessage().startsWith("Unknown type for timestamp_ns")) {
    log.error("Nanos timestamp column stores incompatible type", e);
    throw new SchemaMismatchException(e);
  }
  throw e;
}

Prevention

When it happens

Trigger: Reading a timestamptz_ns / timestamp_ns Iceberg column through StructRowData when the StructLike stores e.g. a BigDecimal, String, Instant, or micros-Long supplied to a nanos-precision getter.

Common situations: Reading tables written by engines that store nanosecond timestamps differently; custom StructLike adapters exposing Instant instead of OffsetDateTime/LocalDateTime; schema promotion from micros to nanos where old data holds a value in the wrong unit/type.

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/c2af360564416f5a. Report an issue: GitHub.

Appendix: source

Thrown at flink/v2.2/flink/src/main/java/org/apache/iceberg/flink/data/StructRowData.java:205

  @Override
  public TimestampData getTimestamp(int pos, int precision) {
    if (precision > 6) {
      Object timeVal = struct.get(pos, Object.class);
      if (timeVal instanceof OffsetDateTime) {
        OffsetDateTime odt = (OffsetDateTime) timeVal;
        return TimestampData.fromEpochMillis(
            odt.toInstant().toEpochMilli(), odt.getNano() % 1_000_000);
      } else if (timeVal instanceof LocalDateTime) {
        LocalDateTime ldt = (LocalDateTime) timeVal;
        return TimestampData.fromEpochMillis(
            ldt.toInstant(ZoneOffset.UTC).toEpochMilli(), ldt.getNano() % 1_000_000);
      } else if (timeVal instanceof Long) {
        long timeLong = (Long) timeVal;
        return TimestampData.fromEpochMillis(
            Math.floorDiv(timeLong, 1_000_000L), (int) Math.floorMod(timeLong, 1_000_000L));
      } else {
        throw new IllegalStateException("Unknown type for timestamp_ns: " + timeVal.getClass());
      }
    }
    long timeLong = getLong(pos);
    return TimestampData.fromEpochMillis(
        Math.floorDiv(timeLong, 1000L), (int) Math.floorMod(timeLong, 1000L) * 1000);
  }

  @Override
  public <T> RawValueData<T> getRawValue(int pos) {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  @Override
  public byte[] getBinary(int pos) {
    return isNullAt(pos) ? null : getBinaryInternal(pos);
  }

  private byte[] getBinaryInternal(int pos) {

View on GitHub (pinned to 86d9c8fc54)