error http

HTTP 422 Unprocessable Entity

Understanding HTTP 422 Unprocessable Entity - the server understands the request but cannot process it due to semantic errors in the content.

What It Means

HTTP 422 Unprocessable Entity indicates that the server understands the content type of the request entity, and the syntax of the request entity is correct, but it was unable to process the contained instructions due to semantic errors.

The key distinction from 400 Bad Request: with 422, the request is well-formed (valid JSON, correct Content-Type), but the data itself fails validation rules.

Common Causes

  • Form validation failures (invalid email format, password too short)
  • Business logic validation errors (end date before start date)
  • Required fields present but with invalid values
  • Data type mismatches (string where number expected)
  • Values outside acceptable ranges
  • Referencing non-existent related resources

How to Fix

Server-side validation (Express.js)

app.post('/api/users', async (req, res) => {
  const errors = [];
  const { email, password, age } = req.body;

  if (!email || !email.includes('@')) {
    errors.push({ field: 'email', message: 'Valid email is required' });
  }
  if (!password || password.length < 8) {
    errors.push({ field: 'password', message: 'Password must be at least 8 characters' });
  }
  if (age && (age < 0 || age > 150)) {
    errors.push({ field: 'age', message: 'Age must be between 0 and 150' });
  }

  if (errors.length > 0) {
    return res.status(422).json({
      error: 'Unprocessable Entity',
      message: 'Validation failed',
      details: errors
    });
  }

  const user = await User.create(req.body);
  res.status(201).json(user);
});

With Zod validation

import { z } from 'zod';

const UserSchema = z.object({
  email: z.string().email('Invalid email format'),
  password: z.string().min(8, 'Password must be at least 8 characters'),
  name: z.string().min(1, 'Name is required'),
  age: z.number().int().min(0).max(150).optional()
});

app.post('/api/users', (req, res) => {
  const result = UserSchema.safeParse(req.body);
  if (!result.success) {
    return res.status(422).json({
      error: 'Validation Error',
      details: result.error.issues
    });
  }
  // Process valid data in result.data
});

Client-side handling

async function submitForm(data) {
  const response = await fetch('/api/users', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(data)
  });

  if (response.status === 422) {
    const { details } = await response.json();
    // Display field-level errors
    details.forEach(error => {
      const field = document.querySelector(`[name="${error.field}"]`);
      showFieldError(field, error.message);
    });
    return;
  }

  return response.json();
}

Django REST Framework

from rest_framework import serializers, status
from rest_framework.response import Response

class UserSerializer(serializers.Serializer):
    email = serializers.EmailField(required=True)
    password = serializers.CharField(min_length=8)
    age = serializers.IntegerField(min_value=0, max_value=150, required=False)

# DRF automatically returns 400 for validation errors
# To return 422 instead:
class UserView(APIView):
    def post(self, request):
        serializer = UserSerializer(data=request.data)
        if not serializer.is_valid():
            return Response(serializer.errors, status=422)
        return Response(serializer.validated_data, status=201)
  • HTTP 400 - Bad Request: The request is syntactically malformed (bad JSON, wrong encoding).
  • HTTP 409 - Conflict: The data is valid but conflicts with existing state.
  • HTTP 415 - Unsupported Media Type: The Content-Type itself is not accepted.