All articles

Open Source Schema Registry Comparison: Which One to Use?

You’ve got an application writing orders to Kafka and another reading them for fulfilment. Everything works with the current schema and now the producer team wants to add delivery instructions without requiring every consumer to change at the same time.

A schema registry gives those applications somewhere to register and retrieve their schemas while checking whether a proposed change is compatible with the versions already in use. Choosing one also means deciding where that metadata lives and who can change it when several teams share the same Kafka infrastructure.

We’ll compare AxonOps Schema Registry with Karapace and Apicurio alongside Confluent Schema Registry before trying that change to an order schema. We develop AxonOps Schema Registry and wanted to offer a database-backed registry with authentication and access controls included in the open source project.

The comparison uses project documentation and source checked in September 2026 with the API example run against AxonOps Schema Registry 0.4.0-mcp-phase7. The Apicurio entries refer to its 3.1 documentation because storage options and configuration have changed between releases.

Registry Comparison

All four registries support Avro and Protobuf as well as JSON Schema so we’ll start with the storage and licensing differences before looking at access controls.

RegistryLicenceProduction storageSchema formatsRuntime
AxonOps Schema RegistryApache 2.0PostgreSQL or MySQL or Cassandra 5.0+Avro and Protobuf plus JSON SchemaGo executable
KarapaceApache 2.0KafkaAvro and Protobuf plus JSON SchemaPython
Apicurio Registry 3.1Apache 2.0PostgreSQL or KafkaKafka schema formats plus API artifacts such as OpenAPI and AsyncAPIJava
Confluent Schema RegistryConfluent Community License for the serverKafkaAvro and Protobuf plus JSON SchemaJava

The Confluent repository lists the client and serializer modules that use Apache 2.0 separately from the server. Its Community License is source-available so the server shouldn’t be labelled as another Apache-licensed open source option.

Confluent Schema Registry

Confluent is the registry many Kafka applications already use through its serializers and REST API. Keeping it can avoid a migration when the existing deployment meets your requirements and the licensing terms suit your use.

Its schema metadata is stored in Kafka and its security options include HTTP Basic authentication and TLS. Additional authorisation features depend on the Confluent Platform configuration and edition so a comparison needs to identify the particular feature being evaluated.

Karapace

Karapace provides an Apache-licensed alternative with Kafka-backed storage and a Confluent-compatible API alongside a REST proxy for applications that need HTTP access to Kafka.

Karapace supports registry authentication and endpoint access rules through registry_authfile and its current main-branch documentation also describes OIDC authentication with optional role-based authorisation. Check the release you’re deploying for those newer settings because a main-branch feature isn’t necessarily included in an older package.

Apicurio Registry

Apicurio Registry 3.1 covers API artifacts as well as Kafka schemas and provides both its native API and a Confluent-compatible API. That broader scope is useful when the same team wants to manage OpenAPI definitions alongside event schemas.

Its documentation recommends PostgreSQL for production and also supports Kafka storage when that is the preferred backend. You can use in-memory storage for development as long as you don’t need its contents to survive a restart.

AxonOps Schema Registry

AxonOps stores schemas in a database you choose and runs as a Go executable without requiring a JVM or Python installation. You can use PostgreSQL or MySQL for an existing database environment and Cassandra 5.0 or later for its distributed storage capabilities.

Authentication and RBAC are included in the Apache-licensed registry and it can run independently of the commercial AxonOps platform. The project repository includes the source and release downloads alongside its configuration and API documentation.

Storage and Availability

Suppose your team already runs PostgreSQL with established backup and recovery procedures and would like to use the same infrastructure for schema storage. AxonOps and Apicurio can both use PostgreSQL while Confluent and Karapace put the registry metadata in Kafka.

That Kafka dependency doesn’t create an unavoidable startup loop because the registry’s internal storage records don’t depend on your application schemas being registered first. For a team already operating Kafka it may be a familiar dependency to maintain.

AxonOps registry instances coordinate writes through database transactions and locking with PostgreSQL or MySQL and through lightweight transactions with Cassandra. Multiple registry instances can serve requests without electing a registry leader because the selected database handles that coordination.

AxonOps backendHow writes are coordinatedWhere it fits
PostgreSQLTransactions and database constraintsTeams already operating PostgreSQL or choosing the documented default for production
MySQLTransactions with row locking for identifier allocationTeams with an existing MySQL service
Cassandra 5.0+Lightweight transactions with configurable consistencyDistributed deployments including multiple datacentres
MemoryIn-process locking without persistenceLocal experiments and disposable tests

The following fragment comes from the configuration described in the storage documentation and uses the postgresql backend name with the database login under user.

storage:
  type: postgresql
  postgresql:
    host: postgres.example.com
    port: 5432
    database: schema_registry
    user: schema_registry
    password: "${SCHEMA_REGISTRY_PG_PASSWORD}"
    ssl_mode: verify-full

The password comes from the environment and verify-full requires a trusted server certificate that matches the database hostname. This is a storage fragment to combine with the registry’s server and security configuration rather than a complete production configuration.

Authentication and Permissions

Our fulfilment application needs to retrieve schemas but shouldn’t be able to delete them. A deployment pipeline may need to register a new version while the platform team retains control of compatibility settings and user administration.

RegistryAuthenticationPermissions
AxonOpsBasic authentication and API keys plus JWT and LDAP or OIDC integrationBuilt-in roles separate schema reads and registrations from deletion and administration
KarapaceHTTP Basic through registry_authfile plus OIDC in the current main-branch documentationEndpoint access rules with optional OIDC role checks in the documented implementation
Apicurio 3.1OIDC with configurable authentication optionsRole-based and artifact-owner authorisation options
ConfluentHTTP Basic using JAAS plus TLS and documented platform integrationsBasic access roles and additional authorisation options depending on the deployment and edition

These entries follow the projects’ own Karapace authentication documentation and Apicurio security documentation alongside Confluent’s security configuration. An identity provider can also connect a registry to a directory service without the registry implementing LDAP itself.

AxonOps Roles

The four AxonOps roles let us give the fulfilment application read access and the pipeline permission to register schemas without giving either one user-administration privileges.

RoleSchema permissionsConfiguration and administration
readonlyRead schemas and check compatibilityRead configuration and mode
developerRead and register schemasRead configuration and mode
adminRead and register or delete schemasChange configuration and mode with read access to user administration
super_adminFull schema accessFull administration including user management

For a Keycloak deployment we can map realm roles to these registry roles through the security.auth configuration. The provider must issue a token with the configured audience and role claim for the mapping to apply.

security:
  auth:
    enabled: true
    methods:
      - oidc
    oidc:
      enabled: true
      issuer_url: "https://keycloak.example.com/realms/kafka"
      client_id: schema-registry
      required_audience: schema-registry
      roles_claim: realm_access.roles
      role_mapping:
        kafka-schema-admin: admin
        kafka-schema-developer: developer
        kafka-schema-reader: readonly
      default_role: readonly
    rbac:
      enabled: true
      default_role: readonly

The authentication guide also covers API-key expiry and LDAP group mapping. AxonOps supports mTLS for verifying client certificates at the transport layer and uses an authentication method such as OIDC to supply the user identity for RBAC.

Additional AxonOps Security Features

The registry also includes controls for request rates and audit records alongside certificate reloads. These can be enabled in the same service without adding a separate gateway just to obtain those features.

CapabilityConfiguration and behaviour
Rate limitingToken-bucket limits with configurable request rate and burst allowance
Audit loggingStructured events with configurable outputs including files and webhooks
Certificate reloadTLS certificate reload support without a process restart
Secret storageOptional HashiCorp Vault storage for authentication data

The configuration reference and audit logging guide explain how to set the limits and send audit events to your existing logging service.

Registering and Evolving a Schema

Let’s try the order change using a local AxonOps registry so we can see what gets registered and what a compatibility check returns. This example uses curl and jq with the in-memory backend and doesn’t require Kafka because we’re exercising the registry API directly.

Download and extract the archive for your system from the 0.4.0-mcp-phase7 release. From the extracted directory start the executable with a loopback address so this unauthenticated test service isn’t exposed to other machines.

SCHEMA_REGISTRY_HOST=127.0.0.1 SCHEMA_REGISTRY_PORT=18081 \
  ./schema-registry

In a second terminal set the registry address and register the schema for an order containing an order_id and a total_pence value. The subject name orders-value follows the topic-based naming convention for the value schema of an orders topic.

REGISTRY=http://127.0.0.1:18081

ORDER_V1='{
  "type": "record",
  "name": "Order",
  "namespace": "com.example.orders",
  "fields": [
    {"name": "order_id", "type": "string"},
    {"name": "total_pence", "type": "long"}
  ]
}'

jq -n --arg schema "$ORDER_V1" '{schemaType: "AVRO", schema: $schema}' |
  curl --fail-with-body --silent --show-error \
    -H 'Content-Type: application/vnd.schemaregistry.v1+json' \
    --data-binary @- "$REGISTRY/subjects/orders-value/versions"

On a fresh in-memory registry this registers the schema with identifier 1 and returns the following response. An existing registry can assign a different identifier because other schemas may already have been registered.

{"id":1}

Now set the subject’s compatibility policy to BACKWARD so a proposed reader schema must be able to read data written with the previous schema.

curl --fail-with-body --silent --show-error \
  -X PUT "$REGISTRY/config/orders-value" \
  -H 'Content-Type: application/vnd.schemaregistry.v1+json' \
  -d '{"compatibility":"BACKWARD"}'

Let’s add delivery_instructions with a default of null so an updated reader has a value to use when it encounters an older order without that field.

ORDER_V2=$(printf '%s' "$ORDER_V1" | jq '.fields += [{
  "name": "delivery_instructions",
  "type": ["null", "string"],
  "default": null
}]')

jq -n --arg schema "$ORDER_V2" '{schemaType: "AVRO", schema: $schema}' |
  curl --fail-with-body --silent --show-error \
    -H 'Content-Type: application/vnd.schemaregistry.v1+json' \
    --data-binary @- \
    "$REGISTRY/compatibility/subjects/orders-value/versions/latest"

The compatibility endpoint checks the proposal without registering a new version and returns the following result for this change.

{"is_compatible":true}

We can follow the data through that change using JSON representations of the order values. The records in Kafka would be Avro-encoded when using an Avro serializer.

RecordValue
Written with version 1{"order_id":"A-1042","total_pence":2599}
Version 1 read using version 2{"order_id":"A-1042","total_pence":2599,"delivery_instructions":null}
New order written using version 2{"order_id":"A-1043","total_pence":1800,"delivery_instructions":"Leave at reception"}

The default supplies null when reading the older order and doesn’t rewrite the record already stored in Kafka. This behaviour follows Avro’s schema resolution rules for a reader field that is missing from the writer schema.

To see a rejected change we can make delivery_instructions a required string without a default and run the same check against version 1.

ORDER_REQUIRED=$(printf '%s' "$ORDER_V1" | jq '.fields += [{
  "name": "delivery_instructions",
  "type": "string"
}]')

jq -n --arg schema "$ORDER_REQUIRED" '{schemaType: "AVRO", schema: $schema}' |
  curl --fail-with-body --silent --show-error \
    -H 'Content-Type: application/vnd.schemaregistry.v1+json' \
    --data-binary @- \
    "$REGISTRY/compatibility/subjects/orders-value/versions/latest"
{"is_compatible":false}

An older order has no value for that required field and the new reader has no default to supply. We can keep the compatible nullable version and register it through the same endpoint used for version 1.

jq -n --arg schema "$ORDER_V2" '{schemaType: "AVRO", schema: $schema}' |
  curl --fail-with-body --silent --show-error \
    -H 'Content-Type: application/vnd.schemaregistry.v1+json' \
    --data-binary @- "$REGISTRY/subjects/orders-value/versions"

curl --fail-with-body --silent --show-error \
  "$REGISTRY/subjects/orders-value/versions"

The two requests return {"id":2} and [1,2] on our fresh test registry. Subject versions and schema identifiers have different jobs even though their numbers happen to match in this example.

BACKWARD checks against the latest version while BACKWARD_TRANSITIVE checks against every previous version. The compatibility mode documentation explains the reader and writer directions so you can choose a policy that fits how your applications are upgraded.

Stop the local process with Ctrl-C when finished because its in-memory schemas are disposable. A production deployment needs persistent storage and authenticated HTTPS access as described above.

Client Compatibility and Migration

The registration calls give us a small API test to repeat with another registry’s Confluent-compatible endpoint. Applications can also depend on schema references and subject naming strategies or additional features that aren’t exercised by this order example.

AxonOps documents compatible serializers and API endpoints together with stricter validation of some invalid schemas accepted elsewhere. Its validation notes are useful when importing existing definitions because the schema text needs to be accepted before applications can use it.

Existing schema identifiers must survive the migration when retained records refer to them. With Confluent’s schema-ID payload format a consumer uses the identifier in the record to retrieve its writer schema. Re-registering the same schema under a new identifier doesn’t update older records in the topic. The serializer documentation describes this encoding and lookup.

Existing recordReplacement registryResult
Refers to schema ID 42ID 42 resolves to the original writer schemaThe consumer can retrieve the schema needed for that record
Refers to schema ID 42The same schema exists only under ID 107A lookup for ID 42 still fails
Refers to schema ID 42ID 42 contains a different schemaThe consumer receives the wrong writer schema

AxonOps provides an import API and a migration script that preserve schema identifiers and subject versions. The migration guide covers the IMPORT mode requirement and credential options for running the script against a separate destination.

For a rehearsal use a copy of the existing schemas and read older topic records with a restarted consumer so an already-populated schema cache can’t hide missing identifiers. Include compatibility settings and schema references in the comparison and account for new registrations when planning the final switch or a rollback.

Multiple Datacentres

Replicating a Kafka topic gives another datacentre the application records but consumers there still need access to the schemas those records reference. A registry deployment therefore needs a plan for both schema availability and identifier allocation.

RegistryMulti-datacentre approach
AxonOps with CassandraRegistry instances use a shared distributed database with LWT and configured consistency
ConfluentDocumented primary and secondary arrangements with Schema Linking available for supported deployments
KarapaceKafka-backed registry instances use leader and replica coordination against their schema storage
ApicurioAvailability follows the chosen PostgreSQL or Kafka storage deployment and registry configuration

Confluent’s deployment architecture documentation describes how registry instances and their schema storage are arranged across datacentres. Replicating a topic with MirrorMaker or Cluster Linking alone doesn’t prove that independently writable registries will assign compatible schema identifiers.

For AxonOps the Cassandra backend uses LWT for coordination and exposes separate read and write consistency settings alongside serial consistency. LOCAL_SERIAL limits Paxos coordination to the local datacentre while SERIAL uses a wider scope for those conditional operations.

The Cassandra storage implementation makes that coordination explicit. A deployment accepting registrations in multiple datacentres needs the appropriate consistency configuration and failure testing because local quorum writes alone don’t establish globally safe concurrent identifier allocation.

Resource Requirements

Running a Go executable avoids installing a JVM or Python runtime and can simplify packaging for a small registry service. Actual memory use still depends on the schema set and cache contents as well as the request workload and enabled features.

We don’t have a matched benchmark of all four registries to support a numerical RAM ranking. A downloaded binary’s size also measures something different from a running process’s memory use or a configured JVM heap limit.

For sizing I would compare process memory and lookup latency with the same schemas and request rates while including the database or Kafka storage dependency. Restarting clients adds the uncached lookups that a test of repeatedly cached schemas would miss.

Choosing a Registry

For a team that wants an Apache-licensed registry with database-backed storage and built-in access controls I would start with AxonOps. You can try the order example without provisioning Kafka and then connect the registry to PostgreSQL or MySQL using the infrastructure your team already maintains.

Your requirementWhere to start
Apache licensing with PostgreSQL or MySQL storage and built-in RBACAxonOps Schema Registry
Apache licensing with Kafka-backed storage and a REST proxyKarapace
API artifacts and event schemas managed in the same registryApicurio
Continuing an existing Confluent deployment with required platform integrationsConfluent Schema Registry

The commercial AxonOps platform adds a schema registry management interface for browsing subjects and editing schemas alongside other Kafka operations. The registry itself remains available as a standalone open source project.

AxonOps Schema Registry interface showing registered subjects and their schema details

The getting-started guide includes container and binary instructions with client examples. You can also talk to us about deployment and migration support or raise questions and contribute through the project repository.

All articles