apache/beam · error · RuntimeException

Unexpected null for value type: " +…

Error message

Unexpected null for value type: " + fieldDescriptor.getName()

What it means

The value-side counterpart in toProtoValue's MAP branch: getMapValueType() returned null for a MAP field, so entry values cannot be converted to the key/value STRUCT expected by BigQuery, and the converter throws naming the proto field.

Solutions

  1. Declare the field as FieldType.map(keyType, valueType) with a concrete value type.
  2. Use generic Map<K,V> in schema classes so the value type is inferred.
  3. Validate all MAP fields have both key and value types before the BigQuery sink.
  4. Handle the RuntimeException at row-conversion time to surface which field is malformed.

Example fix

// before
FieldType m = FieldType.builder().setType(TypeName.MAP).setMapKeyType(FieldType.int64()).build();
// after
FieldType m = FieldType.map(FieldType.int64(), FieldType.double_());
Defensive patterns

Strategy: validation

Validate before calling

for (Schema.Field f : schema.getFields()) {
  if (f.getType().getTypeName() == Schema.TypeName.MAP && f.getType().getMapValueType() == null) {
    throw new IllegalArgumentException("Field " + f.getName() + " missing map value type");
  }
}

Type guard

boolean hasMapValueType(Schema.Field f) {
  return f.getType().getTypeName() != Schema.TypeName.MAP || f.getType().getMapValueType() != null;
}

Try / catch

try {
  value = toProtoValue(fieldDescriptor, beamFieldType, v);
} catch (RuntimeException e) {
  if (e.getMessage().startsWith("Unexpected null for value type")) {
    throw new SchemaException("Map field lacks value type: " + e.getMessage());
  }
  throw e;
}

Prevention

When it happens

Trigger: toProtoValue case MAP where beamFieldType.getMapValueType() returns null while processing row data containing an actual map value.

Common situations: FieldTypes constructed with only a key type; inference from raw/non-generic Maps; schemas shared between pipeline versions where one side lacks value-type metadata.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of apache/beam@12126d8942 (2026-09-13). Data as JSON: /api/errors/2143a0ade618e79c. Report an issue: GitHub.

Appendix: source

Thrown at sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/bigquery/BeamRowToStorageApiProto.java:353

      case ITERABLE:
        Iterable<Object> iterable = (Iterable<Object>) value;
        @Nullable FieldType iterableElementType = beamFieldType.getCollectionElementType();
        if (iterableElementType == null) {
          throw new RuntimeException("Unexpected null element type: " + fieldDescriptor.getName());
        }

        return StreamSupport.stream(iterable.spliterator(), false)
            .map(v -> toProtoValue(fieldDescriptor, iterableElementType, v))
            .collect(Collectors.toList());
      case MAP:
        Map<Object, Object> map = (Map<Object, Object>) value;
        @Nullable FieldType keyType = beamFieldType.getMapKeyType();
        @Nullable FieldType valueType = beamFieldType.getMapValueType();
        if (keyType == null) {
          throw new RuntimeException("Unexpected null for key type: " + fieldDescriptor.getName());
        }
        if (valueType == null) {
          throw new RuntimeException(
              "Unexpected null for value type: " + fieldDescriptor.getName());
        }

        return map.entrySet().stream()
            .map(
                (Map.Entry<Object, Object> entry) ->
                    mapEntryToProtoValue(
                        fieldDescriptor.getMessageType(), keyType, valueType, entry))
            .collect(Collectors.toList());
      default:
        return scalarToProtoValue(fieldDescriptor, beamFieldType, value);
    }
  }

  private static DynamicMessage buildTimestampPicosMessage(
      Descriptor timestampPicosDescriptor, Instant instant) {
    long seconds = instant.getEpochSecond();
    long picoseconds = instant.getNano() * 1000L; // nanos → picos

View on GitHub (pinned to 12126d8942)