> ## Documentation Index
> Fetch the complete documentation index at: https://docs.streamnative.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Pulsar Schema Registry

> Understand the schema registry built into Pulsar brokers on StreamNative Cloud, and how producers and consumers agree on message structure.

Pulsar messages are stored as unstructured byte arrays, and structure is applied to that data only
when it's read. Producers and consumers must therefore agree on the shape of a message, including
its fields and their types.

A Pulsar schema is the metadata that defines how to translate raw message bytes into a structured
type. It acts as a contract between the applications that produce messages and the applications that
consume them: data is serialized into bytes before it's published to a topic, and deserialized back
into a typed object before it's delivered to a consumer.

Every Pulsar cluster on StreamNative Cloud runs a schema registry inside its brokers. The registry
stores registered schema information centrally, so producers and consumers can coordinate the schema
of a topic's messages through the broker rather than out of band.

<Frame>
  <img src="https://mintcdn.com/streamnative/xQF_sK6XJwZcUDec/media/pulsar-schema.svg?fit=max&auto=format&n=xQF_sK6XJwZcUDec&q=85&s=86da03cc6c4b33f1e0ee480067344ba7" alt="Pulsar schema" width="1954" height="1014" data-path="media/pulsar-schema.svg" />
</Frame>

<Note title="Two registries, one cluster">
  A Pulsar cluster with the Kafka protocol enabled also runs the
  [Kafka Schema Registry](/cloud/governance/sr/kafka-schema-registry). The two registries are separate
  systems and are **not** interoperable—a schema registered in one is not visible to the other. See
  [Data governance overview](/cloud/governance/governance-overview) before you build.
</Note>

## Why use a schema

Type safety matters in any system built around messaging and streaming. Raw bytes are flexible, but
that flexibility has a cost: every application has to layer its own type checking and serialization
on top to guarantee that what goes in can be read back out.

A Pulsar schema addresses this by:

* **Enforcing type safety.** Once a topic has a schema, producers and consumers connect only if they
  use a compatible schema.
* **Centralizing schema information.** One location holds the schemas used across your organization,
  which makes sharing them between teams straightforward.
* **Acting as a single source of truth** for the message schemas used across your services.
* **Keeping versions compatible.** When a new schema is uploaded, compatibility rules govern whether
  older consumers can still read the data.
* **Reusing existing storage.** Schemas live in the cluster's existing storage layer. No extra system
  to operate.

## How it works

Pulsar schemas are applied and enforced at the **topic** level. Both producers and consumers can
upload schemas to the broker.

### Producer side

<Frame>
  <img src="https://mintcdn.com/streamnative/xQF_sK6XJwZcUDec/media/pulsar-schema-producer.svg?fit=max&auto=format&n=xQF_sK6XJwZcUDec&q=85&s=bddf01fd923604727777d60b2cbe1e4c" alt="Workflow of Pulsar schema on the producer side" width="1592" height="915" data-path="media/pulsar-schema-producer.svg" />
</Frame>

1. The application builds a producer from a schema instance. That instance defines the schema for the
   data the producer sends. With Avro, for example, Pulsar extracts the schema definition from the
   POJO class and constructs a `SchemaInfo`.
2. The producer connects to the broker, passing the `SchemaInfo` from the schema instance.
3. The broker looks the schema up in the registry. If it's already registered, the broker returns the
   schema version to the producer and the flow ends here.
4. If the schema isn't registered, the broker checks whether schemas on this topic can be updated
   automatically. If not, the schema can't be registered and the broker rejects the producer.
5. Otherwise the broker runs the
   [compatibility check](/cloud/governance/sr/pulsar/compatibility#schema-compatibility-check)
   configured for the topic. If the schema passes, the broker stores it and returns the schema
   version, and every message this producer sends is tagged with that version. If it fails, the
   broker rejects the producer.

### Consumer side

<Frame>
  <img src="https://mintcdn.com/streamnative/xQF_sK6XJwZcUDec/media/pulsar-schema-consumer.svg?fit=max&auto=format&n=xQF_sK6XJwZcUDec&q=85&s=cc144b3365c0c9def567b71318e48236" alt="Workflow of Pulsar schema on the consumer side" width="1548" height="908" data-path="media/pulsar-schema-consumer.svg" />
</Frame>

1. The application builds a consumer from a schema instance.
2. The consumer connects to the broker, passing the `SchemaInfo` from that instance.
3. The broker checks whether the topic is in use—that is, whether it already has a schema, data, an
   active producer, or an active consumer.
4. If the topic isn't in use, the broker checks whether schemas can be updated automatically. If they
   can, it registers the schema and connects the consumer. If not, it rejects the consumer.
5. If the topic is in use, the broker runs the
   [compatibility check](/cloud/governance/sr/pulsar/compatibility#schema-compatibility-check) and
   connects the consumer only if the schema passes.

## What it looks like in code

With a schema, you work in your language's own types instead of hand-rolling serialization. Take a
`User` class:

```java theme={null}
public class User {
   public String name;
   public int age;

   User() {}

   User(String name, int age) {
      this.name = name;
      this.age = age;
   }
}
```

**Without a schema**, a producer can send only `byte[]`, so you serialize the object yourself:

```java theme={null}
Producer<byte[]> producer = client.newProducer()
        .topic(topic)
        .create();
User user = new User("Tom", 28);
byte[] message = // serialize the user yourself
producer.send(message);
```

**With a schema**, you send the object directly:

```java theme={null}
// Send with a JSON schema
Producer<User> producer = client.newProducer(JSONSchema.of(User.class))
        .topic(topic)
        .create();
User user = new User("Tom", 28);
producer.send(user);

// Receive with a JSON schema
Consumer<User> consumer = client.newConsumer(JSONSchema.of(User.class))
   .topic(schemaTopic)
   .subscriptionInitialPosition(SubscriptionInitialPosition.Earliest)
   .subscriptionName("schema-sub")
   .subscribe();
Message<User> message = consumer.receive();
User user = message.getValue();
assert user.age == 28 && user.name.equals("Tom");
```

## Client support

Pulsar schemas are available in the Java, Go, Python, Node.js, C++, and C# clients. Support for
individual schema types varies by client—see
[Schema types](/cloud/governance/sr/pulsar/schema-types) for the details.

## What's next

<CardGroup cols={2}>
  <Card title="Schema types" icon="shapes" href="/cloud/governance/sr/pulsar/schema-types">
    Primitive types, complex types, and auto schemas.
  </Card>

  <Card title="Compatibility" icon="code-compare" href="/cloud/governance/sr/pulsar/compatibility">
    Compatibility strategies, versioning, and safe evolution.
  </Card>

  <Card title="Manage schemas" icon="sliders" href="/cloud/governance/sr/pulsar/manage-schemas">
    Upload, retrieve, and delete schemas with the CLI and REST API.
  </Card>

  <Card title="Use with clients" icon="code" href="/cloud/governance/sr/pulsar/use-with-clients">
    Produce and consume typed messages from your application.
  </Card>
</CardGroup>

<Note title="Attribution">
  Parts of this page are adapted from the [Apache Pulsar documentation](https://pulsar.apache.org/docs/schema-overview/),
  licensed under the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0).
</Note>
