apache/pulsar · error · IncompatibleSchemaException
Incompatible Schema from %s + to %s
Error message
Incompatible Schema from %s + to %s
What it means
When both the existing and the new JSON schemas are in the legacy Jackson format, compatibility is decided by comparing their JSON 'id' fields; if the ids differ the schemas are considered incompatible and the full schema payloads are included in the error message.
Source
Thrown at pulsar-broker/src/main/java/org/apache/pulsar/broker/service/schema/JsonSchemaCompatibilityCheck.java:99
// When legacy format is disabled, the existing schema must be valid Avro.
// If it's not, this is a defense-in-depth rejection (PIP-464).
throw new IncompatibleSchemaException(
"Incompatible schema: existing schema is not in valid Avro format for SchemaType.JSON");
} else {
// broker has schema format with unknown format
// maybe corrupted?
// return true to overwrite
}
}
private static final ObjectReader JSON_SCHEMA_READER =
ObjectMapperFactory.getMapper().reader().forType(JsonSchema.class);
private void isCompatibleJsonSchema(SchemaData from, SchemaData to) throws IncompatibleSchemaException {
try {
JsonSchema fromSchema = JSON_SCHEMA_READER.readValue(from.getData());
JsonSchema toSchema = JSON_SCHEMA_READER.readValue(to.getData());
if (!fromSchema.getId().equals(toSchema.getId())) {
throw new IncompatibleSchemaException(String.format("Incompatible Schema from %s + to %s",
new String(from.getData(), UTF_8), new String(to.getData(), UTF_8)));
}
} catch (IOException e) {
throw new IncompatibleSchemaException(e);
}
}
private boolean isAvroSchema(SchemaData schemaData) {
try {
Schema.Parser fromParser = new Schema.Parser(StructSchemaDataValidator.COMPATIBLE_NAME_VALIDATOR);
fromParser.setValidateDefaults(false);
Schema fromSchema = fromParser.parse(new String(schemaData.getData(), UTF_8));
return true;
} catch (Exception e) {
return false;
}
}View on GitHub (pinned to 820761864e)
Solutions
- Ensure all producers for the topic use POJOs that yield the same JsonSchema id (same schema definition).
- Re-register the topic schema with the new definition in Avro format (delete schema first if needed).
- Migrate off the legacy Jackson format to Avro-format JSON schemas so normal Avro compatibility checks apply.
Example fix
// before: different ids
JsonSchema a = new JsonSchema("com.x.A", "schema1"); // id: schema1
JsonSchema b = new JsonSchema("com.x.B", "schema2"); // id: schema2
// after: aligned id
admin.schemas().createSchema(topic, Schema.JSON(Pojo.class).getSchemaInfo()); Defensive patterns
Strategy: try-catch
Validate before calling
// pre-compare legacy JsonSchema ids client-side
JsonSchema from = MAPPER.readValue(existingBytes, JsonSchema.class);
JsonSchema to = MAPPER.readValue(newBytes, JsonSchema.class);
if (!from.getId().equals(to.getId())) throw new IllegalArgumentException("JsonSchema id mismatch"); Type guard
boolean sameJsonSchemaId(JsonSchema a, JsonSchema b) {
return a != null && b != null && a.getId() != null && a.getId().equals(b.getId());
} Try / catch
try {
admin.schemas().createSchema(topic, schemaInfo);
} catch (PulsarAdminException e) {
if (e.getMessage().startsWith("Incompatible Schema from")) {
// ids differ: register new schema version or use a new topic
}
} Prevention
- Use one POJO per topic so legacy JsonSchema ids stay consistent.
- Avoid multi-team topic sharing with different JSON message types.
- Migrate off legacy Jackson format to Avro-format JSON schemas.
- Compare ids in CI when schema definitions change.
When it happens
Trigger: checkCompatible -> isCompatibleJsonSchema with both schemas parseable as legacy Jackson JsonSchema documents whose getId() values are not equal.
Common situations: Two different POJOs (or the same POJO renamed/refactored) serialized with legacy clients uploading schemas to the same topic; teams reusing a topic across unrelated JSON message types.
Related errors
- Incompatible schema: expected Avro schema format for SchemaT
- Incompatible schema: existing schema is not in valid Avro fo
- The topic has a max partition index of %d, the number of par
- entryFilterNames can't be empty. To remove entry filters use
- The offloadPolicies must be specified for namespace offload.
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/f8eab6934ff26634.
Report an issue: GitHub.