412 vs 428: Precondition Failed vs Precondition Required

412 means a conditional request failed because the resource state changed. 428 means the server requires a conditional request but none was sent.

AspectHTTP 412 — Precondition FailedHTTP 428 — Precondition Required
RFCRFC 9110, Section 15.5.13RFC 6585, Section 3
MeaningThe conditional headers were evaluated; conditions were not metThe server requires conditional headers but none were sent
Client sent If-Match/If-Unmodified-Since?Yes — but the ETag or date did not matchNo — request had no conditional headers
Resource stateResource has changed since the client last read itUnknown — no precondition was provided to verify
CacheableNoNo
Retry behaviorRe-read the resource (GET), get new ETag, then re-send the updateSend the request again with appropriate conditional headers

The Lost Update Problem They Both Prevent

Both codes exist to prevent the “lost update” problem. Without conditional requests, this sequence destroys data: User A reads a resource and gets version 1. User B reads the same resource and gets version 1. User A modifies and saves version 2. User B (still working from version 1) saves version 3 — silently overwriting User A’s changes.

Conditional requests break this: User A saves with If-Match: "etag-v1". This succeeds and creates version 2. User B tries to save with If-Match: "etag-v1". The server has version 2, not version 1, so the ETag does not match. The server returns 412. User B must re-read, see User A’s changes, merge, and retry.

412: The Condition Was Evaluated and Failed

412 Precondition Failed means the client sent a conditional request (using If-Match, If-None-Match, If-Modified-Since, or If-Unmodified-Since) and the condition was not satisfied. The resource has changed since the client last fetched it.

# Client read resource when ETag was "abc"
# Another client updated it; ETag is now "xyz"
PUT /api/documents/42 HTTP/1.1
If-Match: "abc"

HTTP/1.1 412 Precondition Failed
ETag: "xyz"

{"error": "precondition_failed",
 "message": "Document was modified since you last read it"}

The 412 response should include the current ETag so the client can decide whether to merge changes or overwrite.

428: Conditional Headers Were Not Sent At All

428 Precondition Required means the server enforces optimistic locking on this endpoint, but the client sent no conditional headers at all. The server refuses to process an unconditional update because doing so could silently overwrite concurrent changes.

# Client sends a PUT with no If-Match header
PUT /api/documents/42 HTTP/1.1
Content-Type: application/json

{"title": "Updated Title"}

HTTP/1.1 428 Precondition Required
Content-Type: application/json

{"error": "precondition_required",
 "message": "Use If-Match with the current ETag to prevent lost updates"}

The client should GET the document, read the ETag from the response, and re-send the PUT with If-Match: "<etag>".

Decision Rule

Use 412 when the client sent a conditional header and the condition was not met. Use 428 when the server requires conditional headers but the client did not send any. Together they form a complete optimistic locking enforcement mechanism: 428 tells clients they must use ETags; 412 tells them their ETag is stale.

FAQ

Can the same endpoint return both 412 and 428?

Yes. An endpoint that enforces optimistic locking returns 428 to clients that omit conditional headers, and 412 to clients that include them but have a stale ETag. This is the correct implementation of server-side lost-update prevention.

Is 428 widely supported?

It is defined in RFC 6585 and officially registered, but many APIs choose to return 400 or 409 instead, with a custom error message. 428 is semantically precise but requires clients to specifically handle it. Document whichever you use.

What is the difference between 412 on GET vs PUT?

On a GET, 412 is most commonly triggered by If-Match or If-Unmodified-Since — the client only wants the resource if it has not changed. On a PUT or DELETE, it is triggered by the same headers to prevent overwriting concurrent changes. The semantics differ by context.

Full Guides

HTTP 428 Precondition Required — full guide · All comparisons

Related comparisons