A schema that parses as JSON can still be an illegal Avro schema.
JSON.parse only checks brackets and quotes. It has no idea that a union cannot repeat a type, that a default has to match the first branch of a union, or that decimal needs a precision. Those rules live in the Avro specification, not in the JSON grammar, so a formatter that only pretty prints leaves every one of those mistakes for a Kafka consumer or a Spark job to hit at read time. This page runs both checks: JSON syntax first, then the Avro-specific rules, and shows you which one failed.
Why a broken schema still deploys and then breaks something else
Avro has no compiler step of its own. A producer writes bytes against a writer schema, a consumer reads them against a reader schema, and the two only have to agree at the moment of decoding. That means a malformed schema, a union with a default that cannot resolve, an enum missing a symbol another team expects, passes every check in your build pipeline and fails hours later inside a consumer service that has nothing to do with the code you shipped.
The five checks below catch what that failure would have looked like, before it reaches a topic.
What the type keyword is allowed to hold
Every Avro schema node needs a type. What comes after it branches into two very different shapes: eight fixed primitive names, or one of five complex forms that each carry their own required fields.
| Type | Shape | Required alongside "type" |
|---|---|---|
null boolean int long float double bytes string | Primitive | Nothing else. A bare string is the whole schema. |
record / error | Complex, named | name, fields (an array, even if empty) |
enum | Complex, named | name, symbols (a non-empty array of legal names) |
fixed | Complex, named | name, size (positive integer bytes) |
array | Complex, unnamed | items (another schema) |
map | Complex, unnamed | values (another schema, keys are always strings) |
A union is written differently again, as a bare JSON array rather than an object with a type key. ["null", "string"] is a complete, valid schema by itself.
The union default rule nobody reads until it bites
Avro resolves a field default against the first branch of its union, not against whichever branch the default value happens to look like. Put the wrong branch first and a technically sensible default becomes an error.
{"name": "count", "type": ["int", "null"], "default": null}{"name": "count", "type": ["null", "int"], "default": null}Both schemas describe the same set of possible values. Only the second parses under a strict Avro reader, because the default's type is checked against index zero of the union array, and index zero has to be null for a null default to make sense.
This page checks structure, not your data. It confirms the schema itself follows the spec. It cannot tell you whether a record produced somewhere in your pipeline actually matches this schema, and it does not compare two schema versions for compatibility the way a Confluent Schema Registry or Karapace check would before allowing a new version onto a subject. Run this first, then run a compatibility check against your registry if one is in front of the topic.
Logical types change the meaning, not the bytes on the wire
A logicalType is metadata layered on top of a primitive or a fixed. The bytes written to disk stay exactly what the base type would write. Only a reader that knows the annotation interprets them differently, which is what makes a typo here so quiet.
decimalsits onbytesorfixedand needs aprecision, with an optionalscalethat cannot exceed it.dateandtime-millissit onint.time-micros,timestamp-millis, andtimestamp-microssit onlong.uuidsits onstring.
Write "uuid-v4" instead of "uuid", a mistake that reads perfectly reasonably to a person, and every conforming Avro reader ignores it. The field still decodes. It just decodes as a plain string, with no error, no warning, and no hint that the annotation was ever there. That is the one class of mistake this page reports as a note rather than an error, since it is not illegal, only silently ignored.
A duplicate name means two different things depending on where it shows up
Inside one union, a repeated primitive type is a straight error. ["string", "string", "null"] cannot resolve which branch a string value belongs to, so Avro forbids it outright.
Across an entire schema document, a repeated record name is a namespace problem instead. Two records both named Address are fine if one carries com.toolexe.billing as its namespace and the other carries com.toolexe.shipping. Drop the namespace from either one and a schema registry that tracks subjects by full name will see the same identity twice, which is a common source of the "incompatible schema" rejection that shows up in Kafka producer logs with no line number attached.
What five mistakes look like together
Load the broken sample above and the report lists all of it at once: a record name with a space in it, a union repeating string, a default that does not match its union's first branch, a field with no type at all, and (once you fix the rest) a namespace-free record name colliding with nothing in this document but worth a second look anyway. Real schemas rarely fail this hard. They usually fail on exactly one of these, buried in a document three hundred lines long, which is the case this page is built for.
Reach for a different tool when the shape you have is not Avro
Avro schemas are a specific JSON dialect with union arrays, named types, and logicalType annotations. If what you actually have is a JSON document you want indented with no schema rules applied, the JSON beautifier does that without flagging anything as an Avro violation. If a JSON document will not parse at all, start with the JSON fixer to find the syntax break first. Going from JSON data toward Protocol Buffers instead of Avro, the JSON to Proto converter builds a .proto message from a sample payload. Going toward a JSON Schema document for validating API payloads, which is a different specification from Avro despite the name, the JSON to JSON Schema tool covers that instead.
