Skip to content

422 Unprocessable Content

What is HTTP 422 Unprocessable Content?

422 Unprocessable Content
You're making birthday party invitations and your friend writes "February 30th" ...
HTTP 422 Unprocessable Content status code illustration

Explain Like I’m 3

Imagine you’re coloring a picture and Teacher says “use blue for the sky.” You pick up a crayon (good!), but you picked red instead of blue. The crayon works fine - it’s not broken - but you picked the wrong color! Teacher says “I can see you have a crayon, but that’s not the right color for the sky.” That’s what 422 means - the message got there okay, but the information inside isn’t right!

Example: Trying to put a square block in a round hole. The block is perfectly fine, you’re holding it correctly, but it just doesn’t fit in that hole!

Explain Like I’m 5

You’re making birthday party invitations and your friend writes “February 30th” as the date. The invitation looks great - good handwriting, all the fields filled in - but there’s no such thing as February 30th! The invitation itself is fine (it’s not ripped or missing information), but the date doesn’t make sense. That’s a 422 error - the form is correct, but the information in it breaks the rules.

Example: Filling out a form to sign up for soccer, and writing your age as “200 years old.” The form understands what you wrote, but that age doesn’t make sense for a real person!

Jr. Developer

422 Unprocessable Content means the server successfully parsed your request (the JSON is valid, all required fields are present), but the data inside fails validation rules or business logic. This is different from 400 Bad Request, which indicates structural problems with the request itself.

The key distinction:

  • 400 Bad Request: Syntactic errors - can’t parse the request

    • Malformed JSON: {"name": "John",} (trailing comma)
    • Missing required headers
    • Invalid request format
  • 422 Unprocessable Content: Semantic errors - parsed successfully but invalid data

    • Invalid email format: “not-an-email”
    • Values outside allowed range: age = -5
    • Business rule violations: duplicate email address
    • Data type mismatches: sending string where number expected (after parsing)

Common scenarios for 422:

  • Form validation failures (email format, phone number format)
  • Constraint violations (unique username already taken)
  • Business logic errors (can’t ship to that country)
  • Referential integrity issues (foreign key doesn’t exist)
  • Date logic errors (end date before start date)

Code Example

// Express.js with validation middleware
const express = require('express');
const { body, validationResult } = require('express-validator');
const app = express();
app.use(express.json());
// Validation rules for user registration
const userValidation = [
body('email')
.isEmail()
.withMessage('Must be a valid email address'),
body('age')
.isInt({ min: 13, max: 120 })
.withMessage('Age must be between 13 and 120'),
body('username')
.isLength({ min: 3, max: 20 })
.withMessage('Username must be 3-20 characters')
.matches(/^[a-zA-Z0-9_]+$/)
.withMessage('Username can only contain letters, numbers, and underscores')
];
app.post('/api/users', userValidation, async (req, res) => {
// Check for validation errors
const errors = validationResult(req);
if (!errors.isEmpty()) {
// Request was parseable (valid JSON), but data failed validation
return res.status(422).json({
error: 'Unprocessable Content',
message: 'Validation failed',
errors: errors.array()
});
}
// Check business rule: username uniqueness
const existing = await db.users.findOne({
username: req.body.username
});
if (existing) {
return res.status(422).json({
error: 'Unprocessable Content',
message: 'Username already taken',
field: 'username'
});
}
// Create user...
const user = await db.users.create(req.body);
res.status(201).json(user);
});
// Compare with 400 Bad Request for syntax errors
app.use((err, req, res, next) => {
if (err instanceof SyntaxError && err.status === 400 && 'body' in err) {
// Malformed JSON - syntax error
return res.status(400).json({
error: 'Bad Request',
message: 'Invalid JSON syntax',
details: err.message
});
}
next();
});

Crash Course

HTTP 422 Unprocessable Content (renamed from “Unprocessable Entity” in RFC 9110) indicates the server understood the request content type, the syntax is correct, but couldn’t process the instructions due to semantic errors. This status code originated in WebDAV (RFC 4918) but became widely adopted in REST APIs for validation failures.

RFC 9110 Definition: “The 422 (Unprocessable Content) status code indicates that the server understands the content type of the request content, and the syntax of the request content is correct, but it was unable to process the contained instructions.”

The 400 vs 422 Decision Tree:

  1. Can you parse the request? → No → 400 Bad Request
  2. Does it pass validation rules? → No → 422 Unprocessable Content
  3. Is the resource found? → No → 404 Not Found
  4. Does user have permission? → No → 403 Forbidden
  5. Everything OK? → 200/201 Success

Common Validation Categories:

  1. Format Validation

    • Email format: must match RFC 5322
    • Phone numbers: must match E.164 format
    • URLs: must be valid URIs
    • Credit cards: must pass Luhn algorithm
  2. Range Validation

    • Numeric ranges: age 0-120, quantity > 0
    • String length: min/max characters
    • Date ranges: end_date >= start_date
  3. Business Logic Validation

    • Unique constraints: username/email already exists
    • Foreign key constraints: referenced resource doesn’t exist
    • State transitions: can’t cancel a shipped order
    • Inventory: product out of stock
  4. Schema Validation

    • Required fields present
    • Data types correct (after parsing)
    • Enum values valid
    • Nested object structure

Best Practices:

  • Return detailed, actionable error messages
  • Include field names and specific validation failures
  • Use consistent error response format
  • Don’t expose internal validation logic that could aid attacks
  • Consider security: don’t confirm whether usernames exist via validation errors

Code Example

// Comprehensive validation with JSON Schema
const Ajv = require('ajv');
const addFormats = require('ajv-formats');
const ajv = new Ajv({ allErrors: true });
addFormats(ajv);
// JSON Schema for user registration
const userSchema = {
type: 'object',
properties: {
email: {
type: 'string',
format: 'email'
},
username: {
type: 'string',
pattern: '^[a-zA-Z0-9_]{3,20}$'
},
age: {
type: 'integer',
minimum: 13,
maximum: 120
},
password: {
type: 'string',
minLength: 8,
pattern: '^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)'
},
subscribe_newsletter: {
type: 'boolean'
}
},
required: ['email', 'username', 'password'],
additionalProperties: false
};
const validate = ajv.compile(userSchema);
// Validation middleware
function validateSchema(schema) {
return (req, res, next) => {
const valid = validate(req.body);
if (!valid) {
return res.status(422).json({
error: 'Unprocessable Content',
message: 'Request validation failed',
errors: validate.errors.map(err => ({
field: err.instancePath.substring(1) || err.params.missingProperty,
message: err.message,
value: err.data
}))
});
}
next();
};
}
// Business logic validation
async function validateBusinessRules(req, res, next) {
const errors = [];
// Check username uniqueness
const usernameExists = await db.users.exists({
username: req.body.username
});
if (usernameExists) {
errors.push({
field: 'username',
message: 'Username is already taken',
code: 'DUPLICATE_USERNAME'
});
}
// Check email uniqueness
const emailExists = await db.users.exists({
email: req.body.email
});
if (emailExists) {
errors.push({
field: 'email',
message: 'Email is already registered',
code: 'DUPLICATE_EMAIL'
});
}
// Check email domain not on blocklist
const domain = req.body.email.split('@')[1];
const domainBlocked = await db.blockedDomains.exists({ domain });
if (domainBlocked) {
errors.push({
field: 'email',
message: 'Email domain not allowed',
code: 'BLOCKED_DOMAIN'
});
}
if (errors.length > 0) {
return res.status(422).json({
error: 'Unprocessable Content',
message: 'Business validation failed',
errors
});
}
next();
}
app.post('/api/users',
validateSchema(userSchema),
valid...

Deep Dive

HTTP 422 Unprocessable Content represents semantic validation failures where the request is syntactically valid but violates business rules, data constraints, or domain logic. Originally defined in RFC 4918 (WebDAV) as “Unprocessable Entity”, RFC 9110 renamed it to “Unprocessable Content” to align with modern HTTP semantics.

Historical Context: RFC 4918 (WebDAV, 2007) introduced 422 for WebDAV property validation failures. REST API adoption expanded its use beyond WebDAV to general-purpose validation. RFC 9110 (2022) standardized it as “Unprocessable Content” and included it in the core HTTP specification.

Semantic vs Syntactic Validation:

  • Syntactic (400): Request structure invalid - malformed JSON, missing Content-Type, invalid URI encoding
  • Semantic (422): Request structure valid but content violates rules - invalid email format, constraint violations, business logic errors

Validation Architecture Patterns:

  1. Schema-First Validation

    • Define validation rules in schemas (JSON Schema, OpenAPI, GraphQL)
    • Generate validators from schemas
    • Ensures API documentation matches implementation
    • Tools: Ajv, Joi, Yup, Zod
  2. Domain-Driven Validation

    • Validation logic embedded in domain models
    • Rich domain objects with invariants
    • Validation part of entity construction
    • Prevents invalid states at compile time
  3. Pipeline Validation

    • Layered validation (syntax → schema → business logic)
    • Fail fast at each layer
    • Compose validators with middleware/decorators
    • Each layer has specific error codes

Error Response Design:

Industry standards (JSON:API, RFC 7807 Problem Details):

{
"type": "https://api.example.com/errors/validation-failure",
"title": "Validation Failed",
"status": 422,
"detail": "One or more fields failed validation",
"errors": [
{
"field": "email",
"code": "INVALID_FORMAT",
"message": "Email must be valid format",
"value": "not-an-email"
}
]
}

Security Implications:

  1. Information Disclosure

    • Don’t reveal whether usernames/emails exist
    • Generic message: “Invalid credentials” (not “Username not found”)
    • Consider timing attacks in validation
  2. Enumeration Attacks

    • Consistent response times for validation
    • Rate limit validation endpoints
    • Don’t differentiate between “not found” and “forbidden”
  3. Injection Prevention

    • Validate before processing (fail fast)
    • Neve…

Code Example

// Production-grade validation system with multiple strategies
const { z } = require('zod'); // Type-safe schema validation
const { RateLimiterMemory } = require('rate-limiter-flexible');
const crypto = require('crypto');
// Rate limiter for validation endpoints to prevent enumeration
const validationLimiter = new RateLimiterMemory({
points: 10, // 10 validation attempts
duration: 60, // per 60 seconds
});
// Zod schema with custom refinements
const userRegistrationSchema = z.object({
email: z.string().email().max(255),
username: z.string()
.min(3, 'Username must be at least 3 characters')
.max(20, 'Username must be at most 20 characters')
.regex(/^[a-zA-Z0-9_]+$/, 'Username can only contain letters, numbers, and underscores'),
password: z.string()
.min(8, 'Password must be at least 8 characters')
.regex(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])/,
'Password must contain uppercase, lowercase, number, and special character'),
age: z.number().int().min(13).max(120),
terms_accepted: z.literal(true, {
errorMap: () => ({ message: 'You must accept terms and conditions' })
})
}).refine(
data => {
// Cross-field validation: password can't contain username
return !data.password.toLowerCase().includes(data.username.toLowerCase());
},
{ message: 'Password cannot contain username', path: ['password'] }
);
// Custom validation error class
class ValidationError extends Error {
constructor(errors, statusCode = 422) {
super('Validation failed');
this.name = 'ValidationError';
this.statusCode = statusCode;
this.errors = errors;
}
}
// Schema validation middleware
function validateRequest(schema) {
return async (req, res, next) => {
try {
// Parse and validate with Zod
const validated = await schema.parseAsync(req.body);
req.validated = validated;
next();
} catch (err) {
if (err instanceof z.ZodError) {
const errors = err.errors.map(e => ({
field: e.path.join('.'),
code: e.code,
message: e.message,
received: e.received
}));
return res.status(422).json({
type: 'https://api.example.com/errors/validation-failure',
title: 'Validation Failed',
status: 422,
detail: 'Request failed schema validation',
errors
});
}
next(err);
}
};
}
// Business logic validation layer
class UserValidator {
constructor(db) {
this.db = db;
this.validationCache = new Map();
}
async validateUniqueness(field, value, userId = null) {
// Use constant-time comparison to prevent timing attacks
const cacheKey = crypto.createHash('sha256')
.update(`${field}:${value}`)
.digest('hex');
const cached = this.validationCache.get(cacheKey);
if (cached && Date.now() < cached.expiresAt) {
return !cached.exists;
}
const query = { [field]: value };
if (userId) {
...

Frequently Asked Questions

What's the difference between 400 Bad Request and 422 Unprocessable Content?

400 indicates a syntactic error - the request is malformed and can't be parsed (invalid JSON, missing required headers). 422 indicates a semantic error - the request was parsed successfully, but the data violates validation rules or business logic (invalid email format, duplicate username, age out of range).

Should I return 422 or 409 Conflict for duplicate resources?

Use 422 for validation failures during creation (POST) - e.g., 'username already taken'. Use 409 for conflicts during updates (PUT) - e.g., trying to update a resource that was modified by another request (optimistic locking). The distinction: 422 is 'your data fails validation rules', 409 is 'your request conflicts with the current server state'.

What should I include in 422 error responses?

Include: (1) which field(s) failed validation, (2) specific validation error for each field, (3) the invalid value (if not sensitive), (4) what format/range is expected. Use a consistent error format like RFC 7807 Problem Details or JSON:API error format. Don't expose internal validation logic or database constraints that could aid attackers.

Can validation errors reveal security information?

Yes - be careful not to leak information. Don't confirm whether usernames/emails exist via different error messages ('username not found' vs 'incorrect password'). Use generic messages like 'invalid credentials'. Also watch for timing attacks - ensure validation takes similar time whether username exists or not. Consider rate limiting validation endpoints.

Should I validate on the frontend or backend?

Both! Frontend validation provides immediate feedback and better UX. Backend validation is mandatory for security - never trust client-side validation as it can be bypassed. Backend validation should be comprehensive and authoritative. Frontend validation should mirror backend rules but is purely for user convenience.

When should I return 422 vs 415 Unsupported Media Type?

Use 415 when the Content-Type header is unsupported (e.g., client sends XML but server only accepts JSON). Use 422 when the Content-Type is supported, the content parses successfully, but the data inside is invalid. If the server can't understand the Content-Type at all, it's 415; if it understands the format but the data fails validation, it's 422.

How should I handle partial validation failures in batch operations?

Options: (1) All-or-nothing: return 422 if any item fails, process none. (2) Partial success: process valid items, return 207 Multi-Status with individual results for each item. (3) Fail-fast: return 422 on first failure. Choose based on your use case - financial transactions usually require all-or-nothing, while bulk imports might allow partial success.

Common Causes

  • Invalid email format (not matching RFC 5322)
  • Values outside allowed range (negative age, quantity > max)
  • String length violations (password too short, text too long)
  • Pattern/regex mismatches (phone numbers, postal codes)
  • Uniqueness constraint violations (duplicate username/email)
  • Foreign key violations (referenced resource doesn’t exist)
  • Invalid state transitions (canceling shipped order)
  • Business rule violations (insufficient inventory, unauthorized country)
  • Date logic errors (end date before start date, past event date)
  • Cross-field validation failures (password contains username)
  • Required fields missing (after successful parsing)
  • Enum value not in allowed set
  • Invalid data relationships (circular references, orphaned records)

Implementation Guidance

  • Read error response for specific field failures and validation messages
  • Check field formats: ensure emails match pattern ‘user@domain.com
  • Verify numeric ranges: ages 0-120, quantities > 0, etc.
  • Check string lengths: meet minimum/maximum character requirements
  • Ensure uniqueness: use different username/email if already taken
  • Validate foreign keys: ensure referenced resources exist (user_id, product_id)
  • Check state transitions: verify operation is allowed for current resource state
  • Review business rules: ensure request complies with domain logic
  • Validate dates: end dates after start dates, future dates for events
  • Check cross-field dependencies: related fields must be consistent
  • Use frontend validation to catch errors before submission (but always validate server-side)
  • Consult API documentation for field requirements and constraints
  • If validation rules seem wrong, contact API maintainers - don’t try to bypass validation

Comments