This guide walks you through running AxonOps Schema Registry, registering your first schemas, and verifying compatibility. You should have a working registry within five minutes.
- Prerequisites
- Quick Start with Docker
- Quick Start with Binary
- Your First API Calls
- Using with Kafka Clients
- Configuration
- Next Steps
You need one of the following:
- Docker (recommended for quick start) -- any recent version
- Go 1.24+ if building from source
No database is required for initial evaluation. The in-memory storage backend is enabled by default.
Pull and run the registry with a single command:
docker run -d --name schema-registry \
-p 8081:8081 \
ghcr.io/axonops/axonops-schema-registry:latestThe registry starts on port 8081 using in-memory storage. Verify it is running:
curl http://localhost:8081/Expected response:
{}An empty JSON object indicates the registry is healthy and ready to accept requests.
Clone the repository and build:
git clone https://github.com/axonops/axonops-schema-registry.git
cd axonops-schema-registry
make buildThe binary is placed at ./build/schema-registry.
Start the registry with the default in-memory backend:
./build/schema-registryOr with a configuration file:
./build/schema-registry -config config.yamlA minimal configuration file for quick evaluation:
server:
host: "0.0.0.0"
port: 8081
storage:
type: memory
compatibility:
default_level: BACKWARDAll examples below use curl. The registry accepts both application/json and application/vnd.schemaregistry.v1+json content types. Responses always use application/vnd.schemaregistry.v1+json.
curl http://localhost:8081/{}Register a User Avro schema under the subject users-value:
curl -X POST http://localhost:8081/subjects/users-value/versions \
-H "Content-Type: application/vnd.schemaregistry.v1+json" \
-d '{
"schema": "{\"type\":\"record\",\"name\":\"User\",\"namespace\":\"com.example\",\"fields\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"email\",\"type\":\"string\"}]}"
}'Response:
{"id": 1}The registry assigned global schema ID 1. The schemaType field defaults to AVRO when omitted.
Fetch the schema by its global ID:
curl http://localhost:8081/schemas/ids/1{
"schema": "{\"type\":\"record\",\"name\":\"User\",\"namespace\":\"com.example\",\"fields\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"email\",\"type\":\"string\"}]}",
"schemaType": "AVRO"
}Fetch by subject and version:
curl http://localhost:8081/subjects/users-value/versions/1{
"subject": "users-value",
"id": 1,
"version": 1,
"schemaType": "AVRO",
"schema": "{\"type\":\"record\",\"name\":\"User\",\"namespace\":\"com.example\",\"fields\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"email\",\"type\":\"string\"}]}"
}curl http://localhost:8081/subjects["users-value"]Add an age field with a default value. Under BACKWARD compatibility (the default), new fields must have defaults so that consumers using the old schema can still read new data:
curl -X POST http://localhost:8081/subjects/users-value/versions \
-H "Content-Type: application/vnd.schemaregistry.v1+json" \
-d '{
"schema": "{\"type\":\"record\",\"name\":\"User\",\"namespace\":\"com.example\",\"fields\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"email\",\"type\":\"string\"},{\"name\":\"age\",\"type\":\"int\",\"default\":0}]}"
}'{"id": 2}Verify both versions exist:
curl http://localhost:8081/subjects/users-value/versions[1, 2]Before registering a schema, you can test whether it is compatible with existing versions. This checks a proposed schema against the latest version of users-value:
curl -X POST http://localhost:8081/compatibility/subjects/users-value/versions/latest \
-H "Content-Type: application/vnd.schemaregistry.v1+json" \
-d '{
"schema": "{\"type\":\"record\",\"name\":\"User\",\"namespace\":\"com.example\",\"fields\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"email\",\"type\":\"string\"},{\"name\":\"age\",\"type\":\"int\",\"default\":0},{\"name\":\"phone\",\"type\":[\"null\",\"string\"],\"default\":null}]}"
}'{"is_compatible": true}An incompatible change (for example, removing a field without a default) returns {"is_compatible": false}.
Register a JSON Schema by setting schemaType to JSON:
curl -X POST http://localhost:8081/subjects/orders-value/versions \
-H "Content-Type: application/vnd.schemaregistry.v1+json" \
-d '{
"schemaType": "JSON",
"schema": "{\"type\":\"object\",\"properties\":{\"order_id\":{\"type\":\"string\"},\"amount\":{\"type\":\"number\"},\"currency\":{\"type\":\"string\"}},\"required\":[\"order_id\",\"amount\"]}"
}'{"id": 3}Register a Protobuf schema by setting schemaType to PROTOBUF:
curl -X POST http://localhost:8081/subjects/events-value/versions \
-H "Content-Type: application/vnd.schemaregistry.v1+json" \
-d '{
"schemaType": "PROTOBUF",
"schema": "syntax = \"proto3\";\npackage com.example;\n\nmessage Event {\n string event_id = 1;\n string event_type = 2;\n int64 timestamp = 3;\n}"
}'{"id": 4}curl http://localhost:8081/subjects["events-value", "orders-value", "users-value"]curl http://localhost:8081/schemas/types["AVRO", "JSON", "PROTOBUF"]AxonOps Schema Registry is wire-compatible with the Confluent Schema Registry API. Existing Kafka serializers and deserializers work without modification -- point them at your AxonOps Schema Registry URL instead of Confluent's.
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "io.confluent.kafka.serializers.KafkaAvroSerializer");
props.put("schema.registry.url", "http://localhost:8081");
KafkaProducer<String, GenericRecord> producer = new KafkaProducer<>(props);producer, err := kafka.NewProducer(&kafka.ConfigMap{
"bootstrap.servers": "localhost:9092",
})
serializer, err := avro.NewSerializer(
schemaregistry.NewClient(schemaregistry.NewConfig("http://localhost:8081")),
serde.ValueSerde,
avro.NewSerializerConfig(),
)from confluent_kafka import SerializingProducer
from confluent_kafka.schema_registry import SchemaRegistryClient
from confluent_kafka.schema_registry.avro import AvroSerializer
schema_registry_client = SchemaRegistryClient({"url": "http://localhost:8081"})
avro_serializer = AvroSerializer(schema_registry_client, schema_str)
producer = SerializingProducer({
"bootstrap.servers": "localhost:9092",
"value.serializer": avro_serializer,
})The registry uses sensible defaults. The two most common things to configure are the storage backend and the default compatibility level.
The global compatibility level defaults to BACKWARD. To change it at runtime:
curl -X PUT http://localhost:8081/config \
-H "Content-Type: application/vnd.schemaregistry.v1+json" \
-d '{"compatibility": "FULL"}'Available levels: NONE, BACKWARD, BACKWARD_TRANSITIVE, FORWARD, FORWARD_TRANSITIVE, FULL, FULL_TRANSITIVE.
You can also set compatibility per subject:
curl -X PUT http://localhost:8081/config/users-value \
-H "Content-Type: application/vnd.schemaregistry.v1+json" \
-d '{"compatibility": "FULL_TRANSITIVE"}'- Best Practices -- schema design patterns, naming conventions, evolution strategies, and common mistakes
- Installation -- production deployment options, systemd units, and platform-specific packages
- Configuration -- full YAML configuration reference covering server, storage, security, and logging options
- Storage Backends -- choosing between PostgreSQL, MySQL, Cassandra, and in-memory storage
- Authentication -- API keys, LDAP, OIDC, JWT, and role-based access control
- API Reference -- complete documentation for all API endpoints, request/response formats, and error codes