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

  1. Use logical types with identical identifiers on both sides — register/lookup the same LogicalType instance for both fields.
  2. If the underlying representation matches, fall back to the base type (e.g. use FieldType.DATETIME instead of a custom logical type) before merging.
  3. Write a custom merge that widens only primitive parts and preserves one side's logical type.
  4. 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

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


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