6.3 Schema Validation with JSON Schema
Key Takeaways
MongoDB provides server-side schema validation via '$jsonSchema', enforcing document structure, mandatory fields, and value ranges during writes.
The 'bsonType' keyword enforces strict BSON types ('string', 'int', 'long', 'double', 'decimal', 'objectId', 'date') beyond standard JSON primitives.
'validationLevel' controls scope: 'strict' (default) evaluates all writes, while 'moderate' validates inserts and updates to valid docs while exempting legacy invalid docs.
'validationAction' determines failure handling: 'error' (default) rejects the write with DocumentValidationFailure (Code 121), whereas 'warn' logs the violation to mongod.log.
Validation rules are modified dynamically via the 'collMod' command, and setting 'additionalProperties: false' requires explicitly declaring '_id' in properties.
6.3 Schema Validation with JSON Schema
While MongoDB is renowned for its flexible, dynamic document model, enterprise applications require robust data governance, type safety, and structural consistency. Rather than relying solely on client-side Object-Document Mappers (ODMs) like Mongoose or Spring Data—which can be bypassed by direct database connections or microservices written in different languages—MongoDB provides Server-Side Schema Validation.
Schema validation is defined at the collection level using the $jsonSchema operator. Validation rules are enforced natively by the mongod storage engine whenever an insertOne(), insertMany(), updateOne(), updateMany(), replaceOne(), or findAndModify() operation executes.
1. The $jsonSchema Validator Syntax
MongoDB supports the JSON Schema Draft 4 standard, extended with MongoDB-specific keywords—most notably bsonType.
+-----------------------------------------------------------------------------+
| $jsonSchema Core Keywords |
| |
| - bsonType : Enforces specific BSON types ('string', 'int'...) |
| - required : Array of mandatory field names |
| - properties : Map defining validation rules for specific fields |
| - additionalProperties : Boolean / Schema controlling undeclared fields |
| - enum : Array of permitted literal values |
| - minimum / maximum : Inclusive numeric boundary constraints |
| - pattern : Regular expression string match |
| - items : Schema validator applied to array elements |
| - description : Human-readable error/documentation string |
+-----------------------------------------------------------------------------+
Standard BSON Type Identifiers (bsonType)
In JSON Schema, types are typically generic ("number", "string", "object"). In MongoDB $jsonSchema, developers can specify exact BSON type aliases:
"string""int"(32-bit signed integer)"long"(64-bit signed integer)"double"(64-bit IEEE floating-point)"decimal"(128-bit decimal floating-point /Decimal128)"bool""objectId""array""object""date""timestamp""null"
Exam Trap: In the MongoDB Shell (
mongosh), writing{ age: 30 }creates a 64-bit float (double) by default. If a$jsonSchemarule requiresbsonType: "int", inserting{ age: 30 }without explicit casting (such asNumberInt(30)) will fail schema validation! To allow any numeric type, pass an array of types:bsonType: ["int", "long", "double", "decimal"]orbsonType: "number".
2. Defining Schema Validation with db.createCollection()
When initializing a new collection, schema validation rules are supplied inside the options document via the validator parameter:
db.createCollection("users", {
validator: {
$jsonSchema: {
bsonType: "object",
title: "User Document Validation",
required: [ "username", "email", "status", "role", "created_at" ],
additionalProperties: true, // Allows unlisted optional fields
properties: {
_id: {
bsonType: "objectId"
},
username: {
bsonType: "string",
minLength: 3,
maxLength: 30,
description: "'username' must be a string between 3 and 30 characters and is required"
},
email: {
bsonType: "string",
pattern: "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
description: "'email' must match standard email regex format and is required"
},
status: {
enum: [ "PENDING", "ACTIVE", "SUSPENDED", "ARCHIVED" ],
description: "'status' can only be one of the enum values and is required"
},
role: {
enum: [ "USER", "MANAGER", "ADMIN" ],
description: "'role' must be USER, MANAGER, or ADMIN"
},
age: {
bsonType: "int",
minimum: 18,
maximum: 120,
description: "'age' must be an integer between 18 and 120"
},
created_at: {
bsonType: "date",
description: "'created_at' must be a BSON Date and is required"
},
tags: {
bsonType: "array",
minItems: 1,
maxItems: 10,
uniqueItems: true,
items: {
bsonType: "string"
},
description: "'tags' must be an array of up to 10 unique strings"
},
address: {
bsonType: "object",
required: [ "street", "city", "zip" ],
properties: {
street: { bsonType: "string" },
city: { bsonType: "string" },
zip: { bsonType: "string", pattern: "^[0-9]{5}(-[0-9]{4})?$" }
}
}
}
}
},
validationLevel: "strict",
validationAction: "error"
});
3. Validation Levels: strict vs. moderate
The validationLevel option determines which write operations are subject to validation checks:
| Validation Level | Applies to insert Operations | Applies to Updates on Valid Documents | Applies to Updates on Existing Invalid Docs |
|---|---|---|---|
strict (Default) | Enforced | Enforced | Enforced (Update fails if doc remains invalid) |
moderate | Enforced | Enforced | Skipped (Allows updating legacy non-compliant docs) |
off | Disabled | Disabled | Disabled |
+-----------------------------------------------------------------------------+
| validationLevel Execution Flow |
| |
| 1. INCOMING INSERT: |
| Both 'strict' and 'moderate' ALWAYS validate new documents. |
| |
| 2. INCOMING UPDATE: |
| - Document ALREADY conforms to schema: |
| --> Both 'strict' and 'moderate' validate the modified document. |
| |
| - Document was PREVIOUSLY NON-COMPLIANT (Legacy data before validator):|
| --> 'strict': REJECTS the update unless the update fixes all errors. |
| --> 'moderate': PERMITS the update to proceed without error! |
+-----------------------------------------------------------------------------+
When to Use moderate
When introducing schema validation to an existing production database containing legacy documents that violate the new rules, setting validationLevel: "moderate" allows application updates to existing legacy documents without breaking live services, while guaranteeing that all new inserts and updates to modern documents strictly adhere to the schema.
4. Validation Actions: error vs. warn
The validationAction option controls what action MongoDB takes when a write operation violates the validation rules:
| Validation Action | Behavior on Violation | Write Operation Result | Logging & Diagnostic Visibility |
|---|---|---|---|
error (Default) | Rejects write immediately | Throws DocumentValidationFailure (Code 121) | Write is aborted; nothing written to disk |
warn | Accepts write to storage | Write succeeds with acknowledgment | Logs validation violation warning to mongod.log |
Production Migration Strategy with warn
When deploying a new schema validation rule to a mission-critical system, best practice follows a phased migration path:
- Deploy with
validationAction: "warn". - Monitor server diagnostic logs (
mongod.log) for validation warning entries to identify misbehaving client services. - Fix client-side data serialization bugs.
- Promote
validationActionto"error"to enforce hard database-level rejection.
5. Modifying Existing Validators with collMod
Collections cannot be redefined with createCollection() once they exist. To update schema validators, change validation levels, or toggle validation actions on a live collection, use the administrative collMod (Collection Modify) command via db.runCommand().
Syntax for collMod
db.runCommand({
collMod: "users", // Target collection name
validator: {
$jsonSchema: {
bsonType: "object",
required: [ "username", "email", "status", "tier" ], // Added 'tier' as required
properties: {
username: { bsonType: "string" },
email: { bsonType: "string" },
status: { enum: [ "ACTIVE", "INACTIVE" ] },
tier: { enum: [ "BRONZE", "SILVER", "GOLD", "PLATINUM" ] }
}
}
},
validationLevel: "moderate",
validationAction: "error"
});
Inspecting Active Collection Validation Rules
To inspect the active validator, validationLevel, and validationAction on a collection, query db.getCollectionInfos():
// In mongosh:
db.getCollectionInfos({ name: "users" });
// Returns:
[
{
name: "users",
type: "collection",
options: {
validator: { $jsonSchema: { ... } },
validationLevel: "moderate",
validationAction: "error"
},
info: { readOnly: false }
}
]
Completely Removing Validation from a Collection
To remove validation entirely, pass an empty object {} as the validator in collMod:
db.runCommand({
collMod: "users",
validator: {},
validationLevel: "off"
});
6. Testing and Handling Validation Errors
When a write violates schema rules under validationAction: "error", MongoDB halts the write and returns an error payload with error code 121 (DocumentValidationFailure):
// Attempting an invalid insert (missing required 'email' and invalid 'status')
try {
db.users.insertOne({
username: "john_doe",
status: "INVALID_STATUS", // Not in enum
role: "USER",
created_at: new Date()
});
} catch (e) {
console.error("Write failed:", e.message);
// MongoServerError: Document failed validation
// Error Code: 121 (DocumentValidationFailure)
}
The additionalProperties Keyword
additionalProperties: true(Default if omitted): Documents can contain arbitrary fields not declared in thepropertiesmap.additionalProperties: false: Documents are strictly prohibited from containing any fields not explicitly listed inproperties.
Exam Trap: When setting
additionalProperties: false, you must include the_idfield in thepropertiesobject (e.g.,_id: { bsonType: "objectId" }). If_idis omitted fromproperties, every document insertion will fail validation because MongoDB drivers automatically inject_idbefore transmission!
A collection has schema validation enabled with 'validationLevel: "moderate"' and 'validationAction: "error"'. The collection contains an existing legacy document that was inserted before validation was configured and does NOT conform to the schema. An application issues an 'updateOne()' command modifying a valid field on this legacy document. What is the result?
The update fails immediately with a DocumentValidationFailure (Code 121) error
The update succeeds and the document is automatically modified to conform to the schema rules
The update is rejected and the legacy document is moved to a dead-letter quarantine collection
The update succeeds because 'moderate' skips validation checks on updates to existing non-compliant documents
A database administrator configures a collection with 'validationAction: "warn"'. When an application attempts to insert a document that violates the required fields defined in '$jsonSchema', what occurs?
The write operation is rejected and an exception is returned to the client driver
The document is written to the collection and a validation violation is recorded in the MongoDB diagnostic log
The document is converted to BSON and stored without an index entry
MongoDB holds the document in memory until an administrator manually approves the write
Which MongoDB administrative command is used to add or modify schema validation rules on an existing collection without dropping it?
db.runCommand({ collMod: "collectionName", validator: { ... } })
db.collectionName.alterSchema({ validator: { ... } })
db.collectionName.updateValidationRules({ $jsonSchema: { ... } })
db.runCommand({ modifyCollectionSchema: "collectionName", rules: { ... } })
A collection validator defines a rule requiring: 'age: { bsonType: "int", minimum: 21 }'. When executing an insert in the MongoDB Shell using 'db.users.insertOne({ age: 25 })', the write fails with a DocumentValidationFailure error. Why did this validation failure occur?
The minimum operator can only be used with date and string length types in $jsonSchema
The value 25 is less than the required minimum threshold of 21
Numbers entered in the MongoDB Shell without an explicit constructor default to 64-bit float ('double') rather than 32-bit integer ('int')
The $jsonSchema operator requires age to be encapsulated inside an embedded subdocument
Sections you finish are checked off in the contents.