apache/beam · error · IllegalArgumentException
Logical types don't match and cannot be merged: +identifier1
Error message
Logical types don't match and cannot be merged: +identifier1+.v.s+identifier2
What it means
When widening two LOGICAL_TYPE field types, widenNullableTypes requires both logical types to have the same identifier (e.g. both 'Timestamp' or both 'Enumeration<String>'). If the identifiers differ it throws IllegalArgumentException. Note the message concatenates the two identifiers with a literal ".v.s" separator (likely a mangled 'vs').
Source
Thrown at sdks/java/core/src/main/java/org/apache/beam/sdk/schemas/SchemaUtils.java:100
case ITERABLE:
FieldType iterableElementType =
widenNullableTypes(
fieldType1.getCollectionElementType(), fieldType2.getCollectionElementType());
result = FieldType.iterable(iterableElementType);
break;
case MAP:
FieldType keyType =
widenNullableTypes(fieldType1.getMapKeyType(), fieldType2.getMapKeyType());
FieldType valueType =
widenNullableTypes(fieldType1.getMapValueType(), fieldType2.getMapValueType());
result = FieldType.map(keyType, valueType);
break;
case LOGICAL_TYPE:
if (!fieldType1
.getLogicalType()
.getIdentifier()
.equals(fieldType2.getLogicalType().getIdentifier())) {
throw new IllegalArgumentException(
"Logical types don't match and cannot be merged: "
+ fieldType1.getLogicalType().getIdentifier()
+ ".v.s"
+ fieldType2.getLogicalType().getIdentifier());
}
// fall through
default:
result = fieldType1;
}
return result.withNullable(fieldType1.getNullable() || fieldType2.getNullable());
}
/**
* Returns the base type given a logical type and the input type.
*
* <p>This function can be used to handle logical types without knowing InputT or BaseT.
*/
public static <InputT, BaseT> BaseT toLogicalBaseType(View on GitHub (pinned to 12126d8942)
Solutions
- Use logical types with identical identifiers on both sides — register/lookup the same LogicalType instance for both fields.
- If the underlying representation matches, fall back to the base type (e.g. use FieldType.DATETIME instead of a custom logical type) before merging.
- Write a custom merge that widens only primitive parts and preserves one side's logical type.
- Catch IllegalArgumentException, read both identifiers from the message, and unify the LogicalType registrations.
Example fix
// before FieldType t1 = FieldType.logicalType(new MyTimestampType()); // identifier "my-timestamp" FieldType t2 = FieldType.logicalType(SqlTypes.TIMESTAMP); // identifier "Timestamp" Schema merged = SchemaUtils.mergeWideningNullable(s1, s2); // throws // after FieldType t2 = FieldType.logicalType(new MyTimestampType()); // same identifier on both sides Schema merged = SchemaUtils.mergeWideningNullable(s1, s2);
Defensive patterns
Strategy: type-guard
Validate before calling
boolean logicalTypesMatch = IntStream.range(0, Math.min(s1.getFieldCount(), s2.getFieldCount()))
.allMatch(i -> {
FieldType t1 = s1.getField(i).getType(), t2 = s2.getField(i).getType();
if (t1.getTypeName() == TypeName.LOGICAL_TYPE && t2.getTypeName() == TypeName.LOGICAL_TYPE) {
return t1.getLogicalType().getIdentifier()
.equals(t2.getLogicalType().getIdentifier());
}
return true;
}); Type guard
boolean sameLogicalIdentifier(FieldType a, FieldType b) {
return a.getTypeName() != TypeName.LOGICAL_TYPE
|| b.getTypeName() != TypeName.LOGICAL_TYPE
|| a.getLogicalType().getIdentifier()
.equals(b.getLogicalType().getIdentifier());
} Try / catch
try {
Schema merged = SchemaUtils.mergeWideningNullable(s1, s2);
} catch (IllegalArgumentException e) {
if (e.getMessage().startsWith("Logical types don't match")) {
LOG.error("Logical type identifier mismatch: {}", e.getMessage());
throw new SchemaMergeException(e);
}
throw e;
} Prevention
- Register and reuse a single LogicalType instance per domain instead of ad-hoc custom types.
- Prefer built-in logical types (e.g. SqlTypes) over custom ones to keep identifiers stable.
- Document logical-type identifiers as part of your schema compatibility contract.
When it happens
Trigger: Merging schemas where same-position fields are both LOGICAL_TYPE but with different logical type identifiers — e.g. org.apache.beam.sdk.schemas.logicaltypes.SqlTypes.Timestamp vs .Date, or two custom LogicalType implementations with distinct identifiers.
Common situations: Two custom logical types for the same domain registered under different identifier strings; a Beam upgrade changed a built-in logical type's identifier; comparing a schema built with SqlTypes logical types to one built with java.time-backed logical types.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- Unknown logical type " + identifier
- Failed to extract logical type
- Unable to generate coder for schema {schema}
- Expecting exactly one field, found
- The input schema must have exactly one field of type byte.
AI-assisted analysis of apache/beam@12126d8942 (2026-09-13).
Data as JSON: /api/errors/4ac52a1ca45dab49.
Report an issue: GitHub.