{"record":{"id":"4ac52a1ca45dab49","repo":"apache/beam","slug":"logical-types-don-t-match-and-cannot-be-merged-identifier1-v","errorCode":null,"errorMessage":"Logical types don't match and cannot be merged: +identifier1+.v.s+identifier2","messagePattern":"Logical types don't match and cannot be merged: \\+identifier1\\+\\.v\\.s\\+identifier2","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"sdks/java/core/src/main/java/org/apache/beam/sdk/schemas/SchemaUtils.java","lineNumber":100,"sourceCode":"      case ITERABLE:\n        FieldType iterableElementType =\n            widenNullableTypes(\n                fieldType1.getCollectionElementType(), fieldType2.getCollectionElementType());\n        result = FieldType.iterable(iterableElementType);\n        break;\n      case MAP:\n        FieldType keyType =\n            widenNullableTypes(fieldType1.getMapKeyType(), fieldType2.getMapKeyType());\n        FieldType valueType =\n            widenNullableTypes(fieldType1.getMapValueType(), fieldType2.getMapValueType());\n        result = FieldType.map(keyType, valueType);\n        break;\n      case LOGICAL_TYPE:\n        if (!fieldType1\n            .getLogicalType()\n            .getIdentifier()\n            .equals(fieldType2.getLogicalType().getIdentifier())) {\n          throw new IllegalArgumentException(\n              \"Logical types don't match and cannot be merged: \"\n                  + fieldType1.getLogicalType().getIdentifier()\n                  + \".v.s\"\n                  + fieldType2.getLogicalType().getIdentifier());\n        }\n        // fall through\n      default:\n        result = fieldType1;\n    }\n    return result.withNullable(fieldType1.getNullable() || fieldType2.getNullable());\n  }\n\n  /**\n   * Returns the base type given a logical type and the input type.\n   *\n   * <p>This function can be used to handle logical types without knowing InputT or BaseT.\n   */\n  public static <InputT, BaseT> BaseT toLogicalBaseType(","sourceCodeStart":82,"sourceCodeEnd":118,"githubUrl":"https://github.com/apache/beam/blob/12126d8942aaf848030c478b4c6a28c6af861c66/sdks/java/core/src/main/java/org/apache/beam/sdk/schemas/SchemaUtils.java#L82-L118","documentation":"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').","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nFieldType t1 = FieldType.logicalType(new MyTimestampType());   // identifier \"my-timestamp\"\nFieldType t2 = FieldType.logicalType(SqlTypes.TIMESTAMP);       // identifier \"Timestamp\"\nSchema merged = SchemaUtils.mergeWideningNullable(s1, s2); // throws\n// after\nFieldType t2 = FieldType.logicalType(new MyTimestampType()); // same identifier on both sides\nSchema merged = SchemaUtils.mergeWideningNullable(s1, s2);","handlingStrategy":"type-guard","validationCode":"boolean logicalTypesMatch = IntStream.range(0, Math.min(s1.getFieldCount(), s2.getFieldCount()))\n    .allMatch(i -> {\n      FieldType t1 = s1.getField(i).getType(), t2 = s2.getField(i).getType();\n      if (t1.getTypeName() == TypeName.LOGICAL_TYPE && t2.getTypeName() == TypeName.LOGICAL_TYPE) {\n        return t1.getLogicalType().getIdentifier()\n            .equals(t2.getLogicalType().getIdentifier());\n      }\n      return true;\n    });","typeGuard":"boolean sameLogicalIdentifier(FieldType a, FieldType b) {\n  return a.getTypeName() != TypeName.LOGICAL_TYPE\n      || b.getTypeName() != TypeName.LOGICAL_TYPE\n      || a.getLogicalType().getIdentifier()\n          .equals(b.getLogicalType().getIdentifier());\n}","tryCatchPattern":"try {\n  Schema merged = SchemaUtils.mergeWideningNullable(s1, s2);\n} catch (IllegalArgumentException e) {\n  if (e.getMessage().startsWith(\"Logical types don't match\")) {\n    LOG.error(\"Logical type identifier mismatch: {}\", e.getMessage());\n    throw new SchemaMergeException(e);\n  }\n  throw e;\n}","preventionTips":["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."],"tags":["java","beam","schema","logical-type"],"backgroundTag":"schema-validation-failed","analyzedSha":"12126d8942aaf848030c478b4c6a28c6af861c66","analyzedAt":"2026-09-13T01:50:10.254Z","contentChangedAt":"2026-09-13T01:50:10.254Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}