Xerobit
Dev Productivity

When to Use 422 vs 400 (and Other HTTP Status Code Debates)

400 means the request is malformed. 422 means it's valid but semantically wrong. Here's the practical guide to picking the right 4xx for your API responses.

MA Mian Ali Khalid 7 min read
Free tool
HTTP Status Codes
Full HTTP status code reference with explanations and when to use each.
Open tool →
On this page ⌄
  1. 400 Bad Request
  2. 422 Unprocessable Entity
  3. The practical distinction
  4. What the frameworks default to
  5. Other 4xx codes worth using correctly
  6. The error response body
  7. For internal APIs
  8. Further reading

The 4xx range has 29 defined status codes. Most APIs use about five of them, and those five are frequently misused. The debate between 400 and 422 comes up in every API design review, and frameworks actively disagree with each other on the right answer.

Here's the practical guide, what each code actually means, when to use it, and what the frameworks default to.

The HTTP Status Codes reference tool on this site has the full RFC definitions for all 4xx codes. Use it alongside this post.

400 Bad Request

The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing).

RFC 9110

400 means: I couldn't understand the request. The canonical cases:

  • Malformed JSON (syntax error, unclosed bracket)
  • Wrong Content-Type header (sending form-encoded data to an endpoint expecting JSON)
  • Missing required HTTP-level metadata
  • Request body that can't be parsed at all

400 is a transport-level or parsing-level error. The server didn't get far enough to evaluate the content of the request because the form of the request was broken.

POST /api/users
Content-Type: application/json

{name: "Alice", age: 30}   ← Invalid JSON (unquoted keys)

→ 400 Bad Request

422 Unprocessable Entity

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.

RFC 4918 (WebDAV), now referenced in RFC 9110

422 means: I understood the request, but the content is semantically invalid. The server parsed the body successfully but cannot act on it because of business logic or data validation failures:

  • Valid JSON, but email is not a valid email address format
  • Valid JSON, but age is -5 (negative)
  • Valid JSON, but references a resource ID that doesn't exist in a context where it must
  • Valid JSON, but the combination of fields is contradictory
POST /api/users
Content-Type: application/json

{"name": "Alice", "email": "not-an-email", "age": -5}

→ 422 Unprocessable Entity
{
  "errors": [
    {"field": "email", "message": "must be a valid email address"},
    {"field": "age", "message": "must be a positive integer"}
  ]
}

The body was parsed. The server understood what you were trying to do. The data just doesn't pass validation.

The practical distinction

SituationCode
JSON syntax error400
Wrong Content-Type400
Missing required HTTP header400
Valid JSON, field fails format validation422
Valid JSON, field fails business rule422
Valid JSON, references nonexistent related resource422 or 404*
Valid JSON, violates uniqueness constraint409

*Whether a nonexistent related resource is 404 or 422 is a matter of convention. If the endpoint is /api/orders and you send {"user_id": 9999} where user 9999 doesn't exist, some APIs return 404 (not found), some return 422 (validation failure: user_id references a user that doesn't exist). 422 is more informative when paired with a field-level error message.

What the frameworks default to

Ruby on Rails: Rails returns 422 for model validation failures. If a model has validates :email, format: ... and the email is invalid, Rails renders a 422 by default from scaffold controllers.

Django REST Framework: DRF returns 400 for serializer validation failures, even when the JSON is syntactically valid. The DRF maintainers' position is that "invalid data = bad request." You can override this by customizing the exception handler.

FastAPI (Python): FastAPI returns 422 for request body validation failures (via Pydantic), matching the OpenAPI specification, which uses 422 as the standard validation error code.

Express (Node.js): No convention, you choose. Many Express APIs use 400 for everything; some use 422 for semantic errors.

Spring Boot: By default returns 400 for @Valid constraint violations via MethodArgumentNotValidException. You can customize to 422.

The practical upshot: if you're building a public API that will be consumed by third parties, pick one approach and document it. If you're building an internal API with controlled consumers, consistency matters more than which code you pick.

Other 4xx codes worth using correctly

401 vs 403

This is the other major source of confusion:

  • 401 Unauthorized: the request lacks valid authentication credentials. "You haven't identified yourself." Despite the name, it means unauthenticated, not unauthorized.
  • 403 Forbidden: the server understood the request and knows who you are, but refuses to authorize it. "You're identified, but not allowed."
GET /admin/dashboard
(no auth token)
→ 401  "Who are you? Provide credentials."

GET /admin/dashboard
Authorization: Bearer <valid user token>  (user is not an admin)
→ 403  "I know who you are. You don't have permission."

A subtle point: 401 responses must include a WWW-Authenticate header per RFC 9110. Many APIs skip this and still return 401, technically incorrect, but universally accepted.

404 vs 410

  • 404 Not Found: the resource doesn't exist at this URL. It may never have existed, or may have existed and been deleted. The server isn't saying which.
  • 410 Gone: the resource existed here and is permanently gone. Use this when you know the URL is obsolete. Clients (and search engines) can remove it from caches.

410 is underused. Most APIs return 404 for deleted resources when they could return 410 and help clients clean up stale bookmarks.

409 Conflict

Use 409 when the request is valid but conflicts with the current state of the server:

  • Creating a user with an email address that already exists (UNIQUE constraint)
  • Optimistic locking conflict (you submitted an update with an outdated version field)
  • Completing an order that's already been completed
POST /api/users
{"email": "alice@example.com"}  ← already registered

→ 409 Conflict
{"error": "email_taken", "message": "An account with this email already exists."}

415 Unsupported Media Type

When the client sends Content-Type: text/plain to an endpoint that requires application/json. This is more specific than 400, the server isn't complaining about the content, just the declared format.

429 Too Many Requests

Rate limiting. Include a Retry-After header with the number of seconds until the client can try again. This is machine-actionable, clients can back off automatically when they see it.

The error response body

The HTTP status code conveys the category of failure. The body should convey the specifics, ideally in a machine-readable format.

The RFC 9457 (Problem Details for HTTP APIs) format is the current standard:

{
  "type": "https://example.com/errors/validation",
  "title": "Validation Failed",
  "status": 422,
  "detail": "2 fields failed validation.",
  "instance": "/api/users",
  "errors": [
    {"pointer": "/email", "detail": "must be a valid email address"},
    {"pointer": "/age", "detail": "must be a positive integer"}
  ]
}

The pointer field (a JSON Pointer, RFC 6901) tells the client exactly which field failed. This enables client-side form highlighting without string-parsing the error message.

For internal APIs

If you control both the client and server (a mobile app and your own backend, or microservices you own), the distinction between 400 and 422 matters much less. Pick one and be consistent. The important things are:

  1. Return field-level error detail in the body
  2. Use 409 for state conflicts (not 400)
  3. Use 401 vs 403 correctly: clients often need to distinguish "need to log in" from "need different permissions"
  4. Return 429 for rate limiting so clients can back off

Further reading

Keep reading

HTTP 301 vs 302 Redirect: Permanent vs Temporary Redirects Explained

Understand when to use 301 (permanent) vs 302 (temporary) redirects, how they affect SEO page rank transfer, browser caching, and which status code to use for specific...

HTTP 404 Not Found: What It Means and How to Fix It

HTTP 404 means the server understood the request but can't find the resource. It's different from 410 Gone, 301 Moved, and 500 Server Error. Here's when each status applies and...

HTTP 500 Internal Server Error: What It Means and How to Debug It

HTTP 500 means the server encountered an unexpected error it couldn't handle. Unlike 4xx errors, 500 is always the server's fault. Here's how to diagnose and fix HTTP 500 errors.

HTTP Cache Headers: Cache-Control, ETag, and Last-Modified Explained

Master HTTP caching headers: Cache-Control directives (max-age, no-cache, immutable), ETag for conditional requests, and Last-Modified. Learn how to cache static assets forever...

HTTP Redirect Status Codes: 301, 302, 307, 308 Explained

HTTP 3xx redirect codes tell clients to look elsewhere for a resource. Here's when to use 301 vs 302 vs 307 vs 308, how redirects affect SEO, and how to implement redirects in...

HTTP 4xx Client Error Codes: 400, 401, 403, 404, 409, 422, 429 Explained

HTTP 4xx status codes indicate client errors. Here's when to use 400 Bad Request vs 422 Unprocessable Entity, the difference between 401 and 403, what 409 Conflict means, and...

HTTP 5xx Server Error Codes: 500, 502, 503, 504 Explained

HTTP 5xx codes indicate server-side failures. Learn when 500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable, and 504 Gateway Timeout occur, how to handle them...

REST API Error Handling: HTTP Status Codes and Error Response Design

Design consistent REST API error responses using the right HTTP status codes. Learn the difference between 400 Bad Request, 404 Not Found, 409 Conflict, and 422 Unprocessable...

Related tools

Written by Mian Ali Khalid. More guides in Dev Productivity.