Skip to content

Schema Registry

The Schema Registry provides type safety, validation, and schema evolution for your messages. Register schemas once, reference them from producers, and let the broker enforce validation.

  1. Register a schema with the Schema Registry
  2. Link the producer to the schema subject
  3. Send validated messages (broker enforces schema matching)
  4. Consumer fetches the schema and deserializes

Schema client
let client = DanubeClient::builder()
.service_url("http://127.0.0.1:6650")
.build()
.await?;
let schema_client = client.schema();

Register JSON Schema
use danube_client::SchemaType;
let json_schema = r#"{
"type": "object",
"properties": {
"user_id": {"type": "string"},
"event": {"type": "string"},
"timestamp": {"type": "integer"}
},
"required": ["user_id", "event", "timestamp"]
}"#;
let schema_id = schema_client
.register_schema("user-events")
.with_type(SchemaType::JsonSchema)
.with_schema_data(json_schema.as_bytes())
.execute()
.await?;
Register Avro Schema
let avro_schema = r#"{
"type": "record",
"name": "UserEvent",
"namespace": "com.example",
"fields": [
{"name": "user_id", "type": "string"},
{"name": "event", "type": "string"},
{"name": "timestamp", "type": "long"},
{"name": "metadata", "type": ["null", "string"], "default": null}
]
}"#;
let schema_id = schema_client
.register_schema("user-events-avro")
.with_type(SchemaType::Avro)
.with_schema_data(avro_schema.as_bytes())
.execute()
.await?;

Registering the same schema content multiple times returns the existing schema ID (idempotent).


Get latest schema
let schema = schema_client.get_latest_schema("user-events").await?;
println!("Schema ID: {}", schema.schema_id);
println!("Version: {}", schema.version);
println!("Type: {}", schema.schema_type);
List versions
let versions = schema_client.list_versions("user-events").await?;
println!("Available versions: {:?}", versions);

Check if a new schema version is compatible before registering:

Check compatibility
let result = schema_client
.check_compatibility(
"user-events",
new_schema.as_bytes(),
SchemaType::JsonSchema,
None, // Use subject's default mode
)
.await?;
if result.is_compatible {
println!("Safe to register!");
} else {
eprintln!("Incompatible: {:?}", result.errors);
}

Compatibility modes control schema evolution at the subject level. This is an admin-only operation:

ModeDescriptionUse Case
BackwardNew schema reads old dataConsumers upgrade first (default)
ForwardOld schema reads new dataProducers upgrade first
FullBoth backward + forwardCritical schemas
NoneNo validationDevelopment only
Set compatibility mode (admin only)
danube-admin schema set-compatibility \
--subject user-events \
--mode BACKWARD

The most common pattern: link a producer to a schema subject and send typed messages.

Schema-validated producer
// 1. Register schema
schema_client
.register_schema("user-events")
.with_type(SchemaType::JsonSchema)
.with_schema_data(schema.as_bytes())
.execute()
.await?;
// 2. Create producer with schema reference
let mut producer = client
.new_producer()
.with_topic("/default/user-events")
.with_name("event-producer")
.with_schema_subject("user-events") // Uses latest version
.build()?;
producer.create().await?;
// 3. Send typed messages
let event = serde_json::to_vec(&UserEvent {
user_id: "user-123".into(),
event: "login".into(),
timestamp: 1234567890,
})?;
producer.send(event, None).await?;

The examples above use with_schema_subject, which always resolves to the latest version. You can also pin to a specific version or set a minimum:

StrategyMethodBehavior
Latest (default)with_schema_subject("subj")Always uses the newest registered version
Pinnedwith_schema_version("subj", 2)Locked to version 2, even if v3+ exists
Minimumwith_schema_min_version("subj", 2)Uses v2 or any newer compatible version
Version strategies
// Pinned to version 2
.with_schema_version("user-events", 2)
// Minimum version 2 (uses latest >= 2)
.with_schema_min_version("user-events", 2)

When to use each:

  • Latest: most common, automatic upgrades when new versions are registered
  • Pinned: legacy apps that haven’t upgraded, testing specific versions, gradual rollouts
  • Minimum: require a minimum feature set but allow automatic upgrades to compatible versions

Consume messages and deserialize using the schema:

Schema-validated consumer
let mut stream = consumer.receive().await?;
while let Some(message) = stream.recv().await {
let event: UserEvent = serde_json::from_slice(&message.payload)?;
println!("Event: {:?}", event);
consumer.ack(&message).await?;
}

The broker validates schema fields based on the topic’s validation policy:

PolicyBehavior
NoneNo validation; schema fields optional
WarnLogs warning if schema_id missing or invalid; allows message
EnforceRejects message if schema_id missing, invalid, or doesn’t match

Set via admin CLI:

Set validation policy
danube-admin topics set-validation-policy \
--topic /default/user-events \
--policy enforce

TypeFormatUse Case
JSON SchemaJSONWeb APIs, general-purpose
AvroJSON (Avro IDL)Big data, compact binary serialization
Protobuf.proto definitionsHigh-performance RPC, cross-language