JSON Schema required: What It Actually Validates (and What It Doesn't)
The required keyword in JSON Schema validates key presence, not value presence. Understand why null passes in required fields and how to properly close that contract.
There was an email field marked as required in the schema. The payload arrived with "email": null. Validation passed. The null email was persisted to the database. The user didn't receive the confirmation email, and we only discovered it a week later looking at a queue accumulating failures.
The schema was declared correctly — the field was in required. The problem is that required doesn't mean what most devs assume it means.
What required actually validates
The required keyword in JSON Schema validates only one thing: the presence of the key in the object. It doesn't validate the value. It doesn't guarantee the value is non-null. It doesn't prevent the field from existing with value null, "", 0, or any other value you would consider "empty."
If the schema defines:
{
"type": "object",
"properties": {
"email": { "type": "string" }
},
"required": ["email"]
}
These three payloads have different behaviors:
{ "name": "Alice" }
❌ Fails — the email key is absent.
{ "email": null }
✅ Passes — the email key exists. The null value is not validated by the required keyword.
{ "email": "alice@example.com" }
✅ Passes — key present, correct value.
The second case surprises. The specification is clear: required is about the presence of the property, not about the value it carries. null in JSON is not absence — it's an explicit value.
Required field that doesn't accept null
To ensure a required field is not null, you need two separate things: required to require the key's presence, and type to restrict the value.
{
"type": "object",
"properties": {
"email": {
"type": "string",
"format": "email"
}
},
"required": ["email"]
}
With "type": "string", the value null fails because null is not a string. The schema now rejects both the absence of the key and the null value.
The confusion comes from reading required as "mandatory and non-null." In the specification, required means only "mandatory" in the sense of presence. The value restriction is the responsibility of type.
Optional field that accepts null
The reverse also has a pitfall. If you want a field that's not required, but when present can be null or a string:
{
"properties": {
"middle_name": {
"type": ["string", "null"]
}
}
}
Field is not in required, so it can be absent. When present, accepts null or string. This is the correct pattern for truly optional fields where the consumer can explicitly send null to indicate "no value."
If you declare only "type": "string" without required, the field can be absent — but when present, it can't be null. This distinction is relevant for APIs that differentiate "absent field" from "explicitly null field" (JSON Merge Patch, for example, uses null to indicate deletion).
The field is in required but without type — what happens?
{
"type": "object",
"properties": {
"amount": {}
},
"required": ["amount"]
}
An empty property schema {} is a valid schema that accepts any value — including null, false, 0, "", or a nested object. required only requires that the amount key exists. The value can literally be anything.
This pattern appears when someone adds a field to required without defining its restrictions. It's a contract that says "this field must exist" but nothing about what it should contain. That's not what you want in production.
required in nested objects — where people go wrong
required only applies to the schema level where it's declared. If you have a nested object, you need to declare required in it too:
{
"type": "object",
"properties": {
"address": {
"type": "object",
"properties": {
"street": { "type": "string" },
"city": { "type": "string" }
},
"required": ["street", "city"]
}
},
"required": ["address"]
}
required: ["address"] at the root level guarantees the address object exists. The required: ["street", "city"] inside the address schema guarantees those fields exist within the nested object. They're two independent requireds at two different levels.
Without the inner required, you can receive "address": {} — an empty object that passes root-level required validation because the address key exists.
required with additionalProperties: false — the defensive combination
required defines the minimum that must be present. additionalProperties: false defines the maximum — no key outside those declared in properties is accepted. Used together, they close the contract from both sides:
{
"type": "object",
"properties": {
"customer_id": { "type": "string", "format": "uuid" },
"amount": { "type": "number", "minimum": 0.01 },
"currency": { "type": "string", "enum": ["USD", "EUR", "GBP"] }
},
"required": ["customer_id", "amount", "currency"],
"additionalProperties": false
}
This schema rejects payloads with missing fields (via required) and payloads with extra fields (via additionalProperties: false). The contract is exact: neither more, nor less.
To validate this schema before integrating in code, use the JSON Schema Validator — paste the schema and different payload variations to confirm that edge cases behave as you expect.
required and the default field
A frequent confusion: if a field has default defined in the schema, does it still need to be in required?
{
"properties": {
"status": {
"type": "string",
"enum": ["active", "inactive"],
"default": "active"
}
}
}
Yes — default doesn't remove the need for required if you want the field to be mandatory. default in JSON Schema is informational metadata, not a value automatically applied by the validator. AJV has a useDefaults: true option that applies defaults during validation, but this is validator behavior, not the specification. If you depend on default without useDefaults, the absent field passes validation but the default isn't filled in.
The right required is the one that reflects the real contract
The most common mistake isn't using required incorrectly — it's not thinking about what the field actually requires. Before marking a field as required, answer:
- Should the request fail if this field is absent? →
required - Should the request fail if this field is
null? →required+typewithoutnull - Can the field be absent, but when present can't be
null? → withoutrequired,typewithoutnull - Can the field be absent or be
null? → withoutrequired,type: ["string", "null"]
Each combination is a different contract. JSON Schema has keywords for all of them — but it's you who needs to choose which contract you want to enforce.
Note: the editorial content ends here. What follows is a recommendation for a tool related to the post's topic.
Related tool
To test required edge cases before integrating in a validator — payload without the field, with null, with empty string — use the JSON Schema Validator. Paste the schema and the different payloads to confirm the exact behavior of each combination.
- 01 UTF-8 and encoding problems: why accents break and how to fix them What UTF-8 is, code points, mojibake, and why accented text turns to garbage — the mechanism explained end to end.
- 02 MIT, Apache, and GPL Licenses: Practical Differences for Developers MIT, Apache 2.0, and GPL are not the same thing. Understand permissive vs copyleft, patent protection, and compatibility before shipping code to production.