apache/beam · error · java.lang.UnsupportedOperationException
Infer schema of Struct type is not supported.
Error message
Infer schema of Struct type is not supported.
What it means
When translating a protobuf Descriptor to a Beam Schema, ProtoSchemaTranslator tracks visited descriptors to detect recursion. google.protobuf.Struct (the arbitrary-JSON well-known type) has a self-referential shape Beam cannot represent, so it is explicitly rejected with this UnsupportedOperationException; other cyclic references get a different error.
Solutions
- Replace google.protobuf.Struct fields with a concrete message or a map<string, string> if the data is flat.
- Use google.protobuf.Struct outside schema-translated paths (plain ProtoCoder serialization works).
- Represent the JSON as bytes/string and parse downstream.
- Encode well-known types via a custom type registration if supported by your Beam version.
Example fix
// before google.protobuf.Struct metadata = 1; // after map<string, string> metadata = 1; // or a concrete message
Defensive patterns
Strategy: validation
Validate before calling
for (FieldDescriptor f : descriptor.getFields()) {
if (f.getType() == FieldDescriptor.Type.MESSAGE
&& f.getMessageType().getFullName().equals("google.protobuf.Struct")) {
throw new IllegalArgumentException("Struct field unsupported: " + f.getName());
}
} Type guard
boolean hasStruct = descriptor.getFields().stream().anyMatch(f ->
f.getType() == FieldDescriptor.Type.MESSAGE
&& f.getMessageType().getFullName().equals("google.protobuf.Struct")); Try / catch
try { schema = ProtoSchemaTranslator.getSchema(descriptor); } catch (UnsupportedOperationException e) { /* use bytes/string representation or ProtoCoder */ } Prevention
- Avoid google.protobuf.Struct in protos that feed Beam schemas.
- Model dynamic JSON as map fields or opaque bytes.
- Document supported well-known types for your pipeline.
When it happens
Trigger: Translating a proto message that contains a google.protobuf.Struct (or google.protobuf.Value wrapping Struct) field, encountered during recursive schema inference in getSchema.
Common situations: Protos embedding JSON payloads via google.protobuf.Struct; using types generated from well-known types in schema-converted PCollections; Beam SQL / schemas over protos containing Struct fields.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- Cannot create creator for
- Could not decode bytes as message
- Could not encode message as bytes
- DynamicMessage is not allowed for the standard…
- Encountered UNSPECIFIED AtomicType
AI-assisted analysis of apache/beam@12126d8942 (2026-09-13).
Data as JSON: /api/errors/8be7f7b40a9a4560.
Report an issue: GitHub.
Appendix: source
Thrown at sdks/java/extensions/protobuf/src/main/java/org/apache/beam/sdk/extensions/protobuf/ProtoSchemaTranslator.java:184
}
/** Return the proto field number for a type. */
static int getFieldNumber(Field field) {
return field.getOptions().getValue(SCHEMA_OPTION_META_NUMBER);
}
/** Return a Beam schema representing a proto class. */
static Schema getSchema(Class<? extends Message> clazz) {
return getSchema(ProtobufUtil.getDescriptorForClass(clazz));
}
static synchronized Schema getSchema(Descriptors.Descriptor descriptor) {
if (alreadyVisitedSchemas.containsKey(descriptor)) {
@Nullable Schema existingSchema = alreadyVisitedSchemas.get(descriptor);
if (existingSchema == null) {
String name = descriptor.getFullName();
if ("google.protobuf.Struct".equals(name)) {
throw new UnsupportedOperationException("Infer schema of Struct type is not supported.");
}
throw new IllegalArgumentException(
"Cannot infer schema with a circular reference. Proto Field: " + name);
}
return existingSchema;
}
alreadyVisitedSchemas.put(descriptor, null);
/* OneOfComponentFields refers to the field number in the protobuf where the component subfields
* are. This is needed to prevent double inclusion of the component fields.*/
Set<Integer> oneOfComponentFields = Sets.newHashSet();
/* OneOfFieldLocation stores the field number of the first field in the OneOf. Using this, we can use the location
of the first field in the OneOf as the location of the entire OneOf.*/
Map<Integer, Field> oneOfFieldLocation = Maps.newHashMap();
List<Field> fields = Lists.newArrayListWithCapacity(descriptor.getFields().size());
// In proto3, an optional field is internally implemented by wrapping it in a synthetic oneof.
// The Descriptor.getRealOneOfs() method is then used to retrieve only the "real" oneofs that
// you explicitly defined, filtering out these automatically generated ones.View on GitHub (pinned to 12126d8942)