apache/iceberg · error · UnsupportedOperationException

Unsupported value for VARIANT in StructInternalRow: ${value.

Error message

Unsupported value for VARIANT in StructInternalRow: ${value.getClass()}

What it means

toVariantVal converts a VARIANT column's in-memory value into Spark's VariantVal, accepting Iceberg Variant instances (and null). Any other Java class triggers an UnsupportedOperationException naming the unexpected class — the stored representation does not match what the Iceberg variant machinery produces.

Source

Thrown at spark/v4.0/spark/src/main/java/org/apache/iceberg/spark/source/StructInternalRow.java:374

        throw new UnsupportedOperationException("Unsupported array element type: " + elementType);
    }
  }

  private static VariantVal toVariantVal(Object value) {
    if (value instanceof Variant) {
      Variant variant = (Variant) value;
      byte[] metadataBytes = new byte[variant.metadata().sizeInBytes()];
      ByteBuffer metadataBuffer = ByteBuffer.wrap(metadataBytes).order(ByteOrder.LITTLE_ENDIAN);
      variant.metadata().writeTo(metadataBuffer, 0);

      byte[] valueBytes = new byte[variant.value().sizeInBytes()];
      ByteBuffer valueBuffer = ByteBuffer.wrap(valueBytes).order(ByteOrder.LITTLE_ENDIAN);
      variant.value().writeTo(valueBuffer, 0);

      return new VariantVal(valueBytes, metadataBytes);
    }

    throw new UnsupportedOperationException(
        "Unsupported value for VARIANT in StructInternalRow: " + value.getClass());
  }

  @SuppressWarnings("unchecked")
  private <T> GenericArrayData fillArray(
      Collection<?> values, Function<Object[], BiConsumer<Integer, T>> makeSetter) {
    Object[] array = new Object[values.size()];
    BiConsumer<Integer, T> setter = makeSetter.apply(array);

    int index = 0;
    for (Object value : values) {
      if (value == null) {
        array[index] = null;
      } else {
        setter.accept(index, (T) value);
      }

      index += 1;

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. Fix the producer so VARIANT columns hold proper org.apache.iceberg.variant.Variant instances.
  2. Align the Iceberg reader runtime and producing writer versions for variant support.
  3. If values arrive as raw bytes, reconstruct the Variant from metadata+value buffers before inserting into the row.
  4. Use the class name in the message to locate which pipeline stage stored the wrong representation.

Example fix

// before
row.set(ordinal, rawMetadataBytes); // wrong: byte[] stored in VARIANT column
// after
Variant variant = Variants.create(metadataBuffer, valueBuffer);
row.set(ordinal, variant);
Defensive patterns

Strategy: type-guard

Validate before calling

Object v = row.get(ordinal);
if (v != null && !(v instanceof Variant)) {
  throw new IllegalStateException("VARIANT column holds non-Variant: " + v.getClass());
}

Type guard

static boolean isVariantValue(Object v) {
  return v == null || v instanceof Variant;
}

Try / catch

try {
  VariantVal val = structRow.getVariant(ordinal);
} catch (UnsupportedOperationException e) {
  if (e.getMessage().contains("VARIANT")) {
    // decode raw bytes into a Variant, then retry
  } else throw e;
}

Prevention

When it happens

Trigger: Calling getVariant(ordinal) or converting an array of variants on a StructInternalRow where the VARIANT column's value is neither a Variant nor null — e.g. a custom integration stored raw byte[]/String in a variant column, or mixed Iceberg versions deserialized variants differently.

Common situations: Third-party writers putting non-Variant objects into variant columns; writer/reader Iceberg version mismatch around variant materialization; custom data-source integrations bypassing variant encoding.

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