ADL vs JSON Schema
Well-Formed Is Not the Same as Legal
TL;DR: JSON Schema answers exactly one question: is this document well-formed? It’s stateless, single-document, and context-free by design: the right properties for validating interchange between strangers, and the wrong ones for validating writes inside an application. ADL’s field constraints look superficially like JSON Schema’s, and at that level they overlap almost completely. The interesting part is everything past the overlap: unique needs a database, owner needs an identity, immutable needs the previous value, and “enrollment may not exceed the cap” needs every other record. A write is legal or illegal only in context (this user, this state, this history, this data), and context is precisely what a document validator cannot have.
The right tool, precisely scoped
JSON Schema deserves a clean statement of what it’s for, because within that scope it’s excellent. Two parties exchanging JSON need a shared, machine-checkable definition of valid shape: these properties, these types, these bounds, this structure. JSON Schema provides it, neutrally. The same schema validates in a Python service, a TypeScript client, a Java gateway, a CI pipeline. It underpins OpenAPI’s payload definitions, configuration validation across half the tooling ecosystem, and countless API boundaries.
Its power comes from three deliberate properties: it’s stateless (validation depends only on the document), single-document (one instance at a time, no reference to other data), and context-free (no notion of who submitted the document or why). Those properties are what make a schema portable. They’re also a complete list of what application validation needs and JSON Schema, by design, cannot provide.
The overlap, and it’s real
Put an ADL field definition next to a JSON Schema property and the family resemblance is obvious:
# ADL
title: { type: string, required: true, minLength: 3, maxLength: 200 }
price: { type: decimal, min: 0 }
status: { type: string, enum: [draft, review, published] }
Types, required, length bounds, numeric ranges, patterns, enums: this is JSON-Schema-class validation, and ADL enforces all of it at the API boundary on every write, returning structured 422 errors per field. If this were the whole story, ADL’s validation layer would be JSON Schema with different spelling, and this article would be a paragraph.
It’s not the whole story, and the divergence starts inside what looks like the same list.
Constraints that need a world
Walk through ADL’s constraint vocabulary and mark where each one stops being checkable against the document alone.
unique: true — is this email already taken? The document doesn’t know. Answering requires querying the database. JSON Schema has uniqueItems for arrays within one document; uniqueness across documents is categorically outside a stateless validator.
immutable: true — a foreign key or an authorId that must never change after creation. Checking it requires the previous version of the record. A single-document validator has no previous version.
default, autoNow, auto-generated UUIDs are not checks at all, but write-time behavior: the validator would have to modify the document, which a validator, definitionally, doesn’t do. Validation in an application isn’t a gate in front of the write; it’s a stage inside a pipeline that also transforms.
Then the cross-field rules, where ADL’s expression language starts doing what no schema dialect attempts:
validation:
noDowngrade:
rule: "$old == null || priority >= $old.priority"
message: "Priority cannot be downgraded"
when: update
managerInSameDept:
rule: "managerId == null || query('Employee', { id: managerId })[0].departmentId == departmentId"
message: "Manager must be in the same department"
The first rule compares against the record’s stored past. The second reaches into another resource to check consistency. Both evaluate against the merged record state, so a PATCH touching one field is still judged against all of them. JSON Schema 2020-12, even with if/then/else and dependentSchemas (its most heroic conditional machinery), operates on one instance in isolation. $old and query() aren’t missing features; they’re outside the model.
And past validation proper sit the checks that aren’t about the data at all: is this user allowed to set this field (field-level write permissions), is this transition legal from the record’s current state (state machines), does this write violate a policy across records (the rules engine: “total effort commitments for this person may not exceed 100%”, evaluated with an aggregate over other rows, rejected atomically before commit, logged as evidence). Well-formedness is the first check in that chain, and the cheapest one.
The reframe: documents vs. writes
Here’s the clean way to hold the distinction. JSON Schema validates a document: a self-contained artifact, judged against a shape, anywhere, by anyone. ADL validates a write: an attempt by a specific user to change specific data in a specific state of the world. Legality is a property of the attempt, not the payload. The identical JSON body can be legal from an editor and forbidden from an author, legal on a draft and forbidden on a published record, legal today and a policy violation tomorrow when the cap fills.
Once you see the distinction, the industry pattern snaps into focus: every application that “validates with JSON Schema” actually validates with JSON Schema plus a pile of imperative code: the uniqueness checks, the ownership checks, the state checks, the cross-record checks, hand-written in the service layer, undocumented by the schema, drifting from every document that claims to describe them. The schema covers the checkable-in-isolation fraction and lends its name to the whole. ADL’s position is that the whole thing is specifiable: shape, context, and policy in one reviewable document with defined semantics, enforced by the runtime on every write.
Where JSON Schema is simply better
Two concessions.
First, interchange is its home game. If you’re validating documents at a boundary between independent parties (webhook payloads, configuration files, data files, a public API’s inputs as seen by the client’s tooling), a neutral, portable, runtime-independent schema is exactly right, and ADL doesn’t compete there. ADL’s validation is anchored to its runtime on purpose; that anchoring is where the contextual power comes from, and it’s also why you can’t hand an ADL file to a stranger’s validator.
Second, the ecosystem. JSON Schema validators exist for every language, run anywhere, and cost nothing to adopt. The pragmatic bridge: a Kalos domain emits OpenAPI 3.1, whose payload schemas are JSON-Schema-dialect documents, so clients get portable shape validation derived from the same model the server enforces in full. The shape layer travels; the context layer stays home where it works.
The one-line version
JSON Schema tells you the document is well-formed. ADL tells you the write is legal — and legality was always a question about the world, not the payload.
Next in the series: ADL vs BPMN & DMN — the specs that already execute, and the difference between orchestrating systems and being one.