apache/pulsar · error · IncompatibleSchemaException

Incompatible schema: exists schema type %s, new schema type

Error message

Incompatible schema: exists schema type %s, new schema type %s

What it means

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.

Source

Thrown at pulsar-broker/src/main/java/org/apache/pulsar/broker/service/schema/SchemaRegistryServiceImpl.java:348

        schemaStorage.close();
        this.stats.close();
    }

    private SchemaInfo deleted(String schemaId, String user) {
        return new SchemaInfo()
            .setSchemaId(schemaId)
            .setType(SchemaInfo.SchemaType.NONE)
            .setSchema(new byte[0])
            .setUser(user)
            .setDeleted(true)
            .setTimestamp(clock.millis());
    }

    private void checkCompatible(SchemaAndMetadata existingSchema, SchemaData newSchema,
                                 SchemaCompatibilityStrategy strategy) throws IncompatibleSchemaException {
        SchemaData existingSchemaData = existingSchema.schema;
        if (newSchema.getType() != existingSchemaData.getType()) {
            throw new IncompatibleSchemaException(String.format("Incompatible schema: "
                            + "exists schema type %s, new schema type %s",
                    existingSchemaData.getType(), newSchema.getType()));
        }
        SchemaHash existingHash = SchemaHash.of(existingSchemaData);
        SchemaHash newHash = SchemaHash.of(newSchema);
        if (!newHash.equals(existingHash)) {
            compatibilityChecks.getOrDefault(newSchema.getType(), SchemaCompatibilityCheck.DEFAULT)
                    .checkCompatible(existingSchemaData, newSchema, strategy);
        }
    }

    public CompletableFuture<Long> findSchemaVersion(String schemaId, SchemaData schemaData) {
        return trimDeletedSchemaAndGetList(schemaId)
                .thenCompose(schemaAndMetadataList -> {
                    SchemaHash newHash = SchemaHash.of(schemaData);
                    for (SchemaAndMetadata schemaAndMetadata : schemaAndMetadataList) {
                        if (newHash.equals(SchemaHash.of(schemaAndMetadata.schema))) {
                            return completedFuture(((LongSchemaVersion) schemaStorage

View on GitHub (pinned to 820761864e)

Solutions

  1. Use the same SchemaType as the existing schema for the topic (check with pulsar-admin schemas get <topic>).
  2. 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).
  3. Create the new type under a new topic/namespace instead of evolving the old one.
  4. Fix client code that builds SchemaInfo with the wrong type (e.g. Schema.AVRO(...) vs Schema.JSON(...)) so it matches the topic.

Example fix

// before
Producer<User> producer = client.newProducer(Schema.AVRO(User.class)) // topic schema is JSON
    .topic("persistent://tenant/ns/orders").create();
// after
Producer<User> producer = client.newProducer(Schema.JSON(User.class))
    .topic("persistent://tenant/ns/orders").create();
Defensive patterns

Strategy: validation

Validate before calling

SchemaInfo current = admin.schemas().getSchemaInfo(topic);
SchemaType existing = current.getType();
SchemaType incoming = myInfo.getType();
if (existing != incoming) {
    throw new IllegalStateException("Topic schema is " + existing + " but new schema is " + incoming);
}

Type guard

boolean sameType(SchemaInfo existing, SchemaInfo incoming) {
    return existing != null && existing.getType() == incoming.getType();
}

Try / catch

try {
    admin.schemas().createSchema(topic, newInfo);
} catch (PulsarAdminException e) {
    if (e.getMessage() != null && e.getMessage().contains("Incompatible schema: exists schema type")) {
        // existing type differs: decide to delete schema or use matching type
    } else {
        throw e;
    }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of apache/pulsar@820761864e (2026-09-06). Data as JSON: /api/errors/72eb92ca2a24e20c. Report an issue: GitHub.