> ## 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.

# Use JSON Schemas

> Configure the JSON Schema serializer and deserializer, understand open and closed content models, and work through the compatibility rules per construct.

JSON Schema is the most flexible of the three formats and the trickiest to evolve. Its compatibility
semantics hinge on one keyword—`additionalProperties`—and getting that wrong produces the error
most people meet first.

<Note title="JSON Schema is not plain JSON">
  The `JSON` schema type means JSON Schema: a schema document that constrains the data. Producing
  unschematized JSON isn't a registry feature—a record still carries the schema ID prefix.
</Note>

## Configure the serializer

```java theme={null}
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, KafkaJsonSchemaSerializer.class);
props.put(KafkaJsonSchemaSerializerConfig.SCHEMA_REGISTRY_URL_CONFIG, schemaRegistryUrl);
props.put(KafkaJsonSchemaSerializerConfig.BASIC_AUTH_CREDENTIALS_SOURCE, "USER_INFO");
props.put(KafkaJsonSchemaSerializerConfig.USER_INFO_CONFIG, String.format("%s:%s", "any-user", apiKey));
```

And the deserializer:

```java theme={null}
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, KafkaJsonSchemaDeserializer.class);
props.put(KafkaJsonSchemaDeserializerConfig.SCHEMA_REGISTRY_URL_CONFIG, schemaRegistryUrl);
props.put(KafkaJsonSchemaDeserializerConfig.JSON_VALUE_TYPE, Order.class);
```

Add the dependency:

```xml theme={null}
<dependency>
  <groupId>io.confluent</groupId>
  <artifactId>kafka-json-schema-serializer</artifactId>
  <version>7.5.0</version>
</dependency>
```

## Provide the schema

Annotate your class so the serializer can derive a schema from it:

```java theme={null}
@Schema(value = "{"
    + "\"type\":\"object\","
    + "\"properties\":{"
    + "  \"id\":{\"type\":\"string\"},"
    + "  \"total\":{\"type\":\"number\"}"
    + "},"
    + "\"required\":[\"id\"],"
    + "\"additionalProperties\":false"
    + "}", refs = {})
public class Order {
    public String id;
    public double total;
}
```

Or send a `JsonNode` directly when you have no class to annotate—the serializer uses the schema
registered for the subject.

## Open and closed content models

This is the concept that governs JSON Schema evolution.

* **Closed** (`"additionalProperties": false`)—the document may contain only the declared
  properties. Anything else fails validation.
* **Open** (`additionalProperties` absent or `true`)—undeclared properties are permitted.

The default is **open**, because `additionalProperties` defaults to `true` in JSON Schema.

An open model makes adding a property awkward. Under an open schema, data written before the property
existed may already have contained a value under that name with a different type—so the registry
can't prove that adding it is safe.

<Warning title="The open-content-model error">
  Adding a property to an open schema produces:

  ```
  The new schema has an open content model and has a property not present in the old schema
  ```

  Two ways out:

  1. **Close the model.** Set `"additionalProperties": false` on the *original* schema before you add
     properties. This is the durable fix, and worth doing on any new subject from the start.
  2. **Declare the new property on the old schema too**, so both versions agree it exists.

  Closing the model on a schema that already has versions is itself a compatibility change, so make
  that decision when you create the subject.
</Warning>

## Compatibility rules per construct

| Construct                     | Backward-compatible change                                                                                             |
| ----------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| **Primitive types**           | Widening only—`integer` → `number`. Narrowing is breaking.                                                             |
| **Objects**                   | Add an optional property to a closed model. Remove a property from `required`. Adding a required property is breaking. |
| **Enums**                     | Add a value only if readers tolerate unknown values. Removing a value is breaking.                                     |
| **Arrays**                    | Relax `items` constraints. Tightening them is breaking.                                                                |
| **Unions** (`oneOf`, `anyOf`) | Add a branch under forward compatibility. Removing a branch is breaking.                                               |

Moving a property between `required` and optional is the most common intentional change, and the
direction matters: making a property optional is backward compatible, making one required is not.

The default compatibility mode is `BACKWARD`. See
[Evolution and compatibility](/kafka/governance/sr/fundamentals/schema-evolution).

## Schema references

JSON Schema references another schema through `$ref`, with the reference `name` matching the `$ref`
value:

```json theme={null}
{
  "type": "object",
  "properties": {
    "address": {"$ref": "address.json"}
  },
  "additionalProperties": false
}
```

```json theme={null}
"references": [
  {"name": "address.json", "subject": "com.acme.Address", "version": 1}
]
```

<Warning title="External URLs are rejected">
  A `$ref` pointing at an external URL—`https://example.com/schemas/address.json`—is rejected. Every
  reference must resolve to a schema registered in this registry. See
  [Schema references](/kafka/governance/sr/fundamentals/schema-references).
</Warning>

## Normalization for JSON Schema

`?normalize=true` performs real normalization for Avro and Protobuf. **For JSON Schema it does not
add semantic normalization**—however, the schema is still reserialized before storage, so
whitespace-only differences between two submissions don't create separate schemas.

The gap is **field ordering**: two JSON schemas that are semantically identical but list properties in
a different order are stored as two distinct schemas with two distinct IDs, and each registration
creates a new version. If your build serializes schemas non-deterministically, you can accumulate
versions that are semantically identical.

Serialize your schemas with sorted keys and a stable property order before registering them.

## Next steps

<CardGroup cols={2}>
  <Card title="Avro" icon="file-code" href="/kafka/governance/sr/fundamentals/formats/avro">
    Defaults, aliases, and schema resolution.
  </Card>

  <Card title="Protobuf" icon="code" href="/kafka/governance/sr/fundamentals/formats/protobuf">
    Field numbers, imports, and null handling.
  </Card>
</CardGroup>
