{"record":{"id":"72eb92ca2a24e20c","repo":"apache/pulsar","slug":"incompatible-schema-exists-schema-type-s-new-sc","errorCode":null,"errorMessage":"Incompatible schema: exists schema type %s, new schema type %s","messagePattern":"Incompatible schema: exists schema type (.+?), new schema type (.+?)","errorType":"exception","errorClass":"IncompatibleSchemaException","httpStatus":null,"severity":"error","filePath":"pulsar-broker/src/main/java/org/apache/pulsar/broker/service/schema/SchemaRegistryServiceImpl.java","lineNumber":348,"sourceCode":"        schemaStorage.close();\n        this.stats.close();\n    }\n\n    private SchemaInfo deleted(String schemaId, String user) {\n        return new SchemaInfo()\n            .setSchemaId(schemaId)\n            .setType(SchemaInfo.SchemaType.NONE)\n            .setSchema(new byte[0])\n            .setUser(user)\n            .setDeleted(true)\n            .setTimestamp(clock.millis());\n    }\n\n    private void checkCompatible(SchemaAndMetadata existingSchema, SchemaData newSchema,\n                                 SchemaCompatibilityStrategy strategy) throws IncompatibleSchemaException {\n        SchemaData existingSchemaData = existingSchema.schema;\n        if (newSchema.getType() != existingSchemaData.getType()) {\n            throw new IncompatibleSchemaException(String.format(\"Incompatible schema: \"\n                            + \"exists schema type %s, new schema type %s\",\n                    existingSchemaData.getType(), newSchema.getType()));\n        }\n        SchemaHash existingHash = SchemaHash.of(existingSchemaData);\n        SchemaHash newHash = SchemaHash.of(newSchema);\n        if (!newHash.equals(existingHash)) {\n            compatibilityChecks.getOrDefault(newSchema.getType(), SchemaCompatibilityCheck.DEFAULT)\n                    .checkCompatible(existingSchemaData, newSchema, strategy);\n        }\n    }\n\n    public CompletableFuture<Long> findSchemaVersion(String schemaId, SchemaData schemaData) {\n        return trimDeletedSchemaAndGetList(schemaId)\n                .thenCompose(schemaAndMetadataList -> {\n                    SchemaHash newHash = SchemaHash.of(schemaData);\n                    for (SchemaAndMetadata schemaAndMetadata : schemaAndMetadataList) {\n                        if (newHash.equals(SchemaHash.of(schemaAndMetadata.schema))) {\n                            return completedFuture(((LongSchemaVersion) schemaStorage","sourceCodeStart":330,"sourceCodeEnd":366,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/schema/SchemaRegistryServiceImpl.java#L330-L366","documentation":"SchemaRegistryServiceImpl.checkCompatible rejects a schema update whose SchemaType differs from the currently stored schema (SchemaRegistryServiceImpl.java:348). Type compatibility (e.g. existing JSON vs new Avro) is never allowed, independent of strategy; the message reports both the existing and the new type. After the type check passes, schema hash equality is what permits a same-type update.","triggerScenarios":"Uploading a schema via admin.schemas().createSchema/uploadSchema (or producer schema registration) to a topic that already has a schema of a different type — e.g. topic registered as JSON, now pushing Avro; or an AVRO vs PROTOBUF_NATIVE switch; also hits when a consumer's auto-detected schema resolves to a different type than the stored one.","commonSituations":"Migrating serialization format (JSON -> Avro) without deleting/resetting the schema; two teams independently picked different schema frameworks for the same topic; client library defaulting to AVRO while the topic was created with JSON; re-registering after a type was changed in the data model.","solutions":["Use the same SchemaType as the existing schema for the topic (check with pulsar-admin schemas get <topic>).","If the type change is intentional, delete the schema first: pulsar-admin schemas delete <topic>, then register the new type (existing data may not deserialize under the new schema).","Create the new type under a new topic/namespace instead of evolving the old one.","Fix client code that builds SchemaInfo with the wrong type (e.g. Schema.AVRO(...) vs Schema.JSON(...)) so it matches the topic."],"exampleFix":"// before\nProducer<User> producer = client.newProducer(Schema.AVRO(User.class)) // topic schema is JSON\n    .topic(\"persistent://tenant/ns/orders\").create();\n// after\nProducer<User> producer = client.newProducer(Schema.JSON(User.class))\n    .topic(\"persistent://tenant/ns/orders\").create();","handlingStrategy":"validation","validationCode":"SchemaInfo current = admin.schemas().getSchemaInfo(topic);\nSchemaType existing = current.getType();\nSchemaType incoming = myInfo.getType();\nif (existing != incoming) {\n    throw new IllegalStateException(\"Topic schema is \" + existing + \" but new schema is \" + incoming);\n}","typeGuard":"boolean sameType(SchemaInfo existing, SchemaInfo incoming) {\n    return existing != null && existing.getType() == incoming.getType();\n}","tryCatchPattern":"try {\n    admin.schemas().createSchema(topic, newInfo);\n} catch (PulsarAdminException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"Incompatible schema: exists schema type\")) {\n        // existing type differs: decide to delete schema or use matching type\n    } else {\n        throw e;\n    }\n}","preventionTips":["Always fetch the current schema (pulsar-admin schemas get) before registering a new one.","Keep one serialization framework per topic; document the topic's schema type for all producer teams.","For intentional type migration, plan a new topic instead of deleting the schema on the live topic."],"tags":["pulsar","schema-registry","avro","json"],"backgroundTag":"schema-type-mismatch","analyzedSha":"820761864ed8e2a7d2e52dd9763ad2ae117c1395","analyzedAt":"2026-09-06T00:14:20.138Z","contentChangedAt":"2026-09-06T00:14:20.138Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}