apache/beam · error · UnsupportedOperationException

Unsupported Beam type for Iceberg timestamp with timezone: {

Error message

Unsupported Beam type for Iceberg timestamp with timezone: {valueClass}

What it means

getIcebergTimestampValue converts a Beam value into an Iceberg timestamptz (OffsetDateTime). It accepts java.time.Instant, Long (micros since epoch), and ISO-8601 String; any other Beam runtime type is rejected with UnsupportedOperationException so a silent data corruption never reaches the Iceberg table.

Source

Thrown at sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/IcebergUtils.java:487

  private static Object getIcebergTimestampValue(Object beamValue, boolean shouldAdjustToUtc) {
    // timestamptz
    if (shouldAdjustToUtc) {
      if (beamValue instanceof java.time.Instant) { // MicrosInstant
        OffsetDateTime epoch = java.time.Instant.ofEpochSecond(0).atOffset(ZoneOffset.UTC);
        java.time.Instant instant = (java.time.Instant) beamValue;
        long nanosFromEpoch =
            TimeUnit.SECONDS.toNanos(instant.getEpochSecond()) + instant.getNano();
        return ChronoUnit.NANOS.addTo(epoch, nanosFromEpoch);
      } else if (beamValue instanceof LocalDateTime) { // SqlTypes.DATETIME
        return OffsetDateTime.of((LocalDateTime) beamValue, ZoneOffset.UTC);
      } else if (beamValue instanceof Instant) { // FieldType.DATETIME
        return DateTimeUtil.timestamptzFromMicros(((Instant) beamValue).getMillis() * 1000L);
      } else if (beamValue instanceof Long) { // FieldType.INT64
        return DateTimeUtil.timestamptzFromMicros((Long) beamValue);
      } else if (beamValue instanceof String) { // FieldType.STRING
        return OffsetDateTime.parse((String) beamValue).withOffsetSameInstant(ZoneOffset.UTC);
      } else {
        throw new UnsupportedOperationException(
            "Unsupported Beam type for Iceberg timestamp with timezone: " + beamValue.getClass());
      }
    }

    // timestamp
    if (beamValue instanceof java.time.Instant) { // MicrosInstant
      java.time.Instant instant = (java.time.Instant) beamValue;
      return DateTimeUtil.timestampFromNanos(
          TimeUnit.SECONDS.toNanos(instant.getEpochSecond()) + instant.getNano());
    } else if (beamValue instanceof LocalDateTime) { // SqlType.DATETIME
      return beamValue;
    } else if (beamValue instanceof Instant) { // FieldType.DATETIME
      return DateTimeUtil.timestampFromMicros(((Instant) beamValue).getMillis() * 1000L);
    } else if (beamValue instanceof Long) { // FieldType.INT64
      return DateTimeUtil.timestampFromMicros((Long) beamValue);
    } else if (beamValue instanceof String) { // FieldType.STRING
      return LocalDateTime.parse((String) beamValue);
    } else {

View on GitHub (pinned to 12126d8942)

Solutions

  1. Check the Beam Row field's declared FieldType: for a timestamptz column use FieldType.DATETIME with values that are java.time.Instant (or FieldType.INT64 micros / FieldType.STRING ISO-8601)
  2. Convert the value before writing: if you have a joda DateTime call instant.toInstant() or new java.time.Instant(...); for java.util.Date use date.toInstant()
  3. If the value is a String, ensure it is ISO-8601 parseable by OffsetDateTime.parse, otherwise pre-normalize it
  4. Log the offending class (beamValue.getClass()) and trace where the Row was created to fix the producer

Example fix

// before (producer built joda-based value)
Row row = Row.withSchema(schema).addValues(DateTime.now()).build();
// after
Row row = Row.withSchema(schema).addValues(java.time.Instant.now()).build();
Defensive patterns

Strategy: type-guard

Validate before calling

if (!(v instanceof java.time.Instant) && !(v instanceof Long) && !(v instanceof String)) {
  throw new IllegalArgumentException("Field '" + name + "' must be Instant/Long/String for timestamptz, got " + v.getClass());
}

Type guard

boolean isTimestamptzCompatible(Object v) {
  return v instanceof java.time.Instant || v instanceof Long || v instanceof String;
}

Try / catch

try {
  icebergRecord = copyFieldIntoRecord(...);
} catch (UnsupportedOperationException e) {
  LOG.error("timestamptz conversion failed: {}", e.getMessage());
  // coerce value to Instant or skip/retry with corrected schema
}

Prevention

When it happens

Trigger: copyFieldIntoRecord maps a Beam Row field into an Iceberg record for a timestamptz column, but the Beam value is none of Instant, Long, or String — e.g. a Beam Row built with FieldType.DATETIME (org.joda.time.DateTime / ReadableInstant other than Instant), a boxed Integer, a byte array, or a custom logical type object.

Common situations: Reading records from another source (e.g. Avro/Parquet/JDBC) whose date-time decoder produced a different class (joda DateTime, java.util.Date, Timestamp); a Beam schema inferred a LOGICAL_TYPE whose runtime representation is not Instant; someone hand-built Rows with the wrong FieldType mapping.

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/beam@12126d8942 (2026-09-13). Data as JSON: /api/errors/8c3e25f6cd21bf40. Report an issue: GitHub.