When an API returns 412 and when it returns 409
Both statuses signal a conflict and the two are used interchangeably in the wild, but they answer different questions.
A 412 means a precondition you sent failed: you supplied If-Match with a version and the resource has moved on. A 409 means the request conflicts with current state for a reason you did not test for. The practical difference is that 412 tells you exactly which check failed, while 409 requires reading the body to find out.
The distinguishing question
Did the client state a condition? If it sent If-Match, If-Unmodified-Since or a version field and that condition is no longer true, the answer is 412. If the client stated nothing and the server is refusing on semantic grounds — a duplicate name, a state machine violation — the answer is 409.
Why 412 is the more useful of the two
A 412 is machine-actionable: the client knows the version it sent is stale and can re-read, merge and retry without parsing anything. A 409 usually needs a human or a bespoke handler, because the conflict could be any of several things.
The third status people forget
A 428 Precondition Required means the server will not accept unconditional writes at all. It is how an API forces every client into optimistic locking rather than hoping they opt in.
What the response body should carry
A 412 needs almost nothing in the body, because the status already says what failed and the client knows what to do. A 409 needs an explicit machine-readable reason, since the client cannot otherwise tell a duplicate name from an illegal transition from a version clash. An API returning bare 409s forces every consumer to guess or to parse prose.
Which status applies
Which status applies
| Situation | Status |
| If-Match sent, version stale | 412 |
| If-Unmodified-Since sent, resource newer | 412 |
| No precondition sent, server requires one | 428 |
| Duplicate unique field | 409 |
| Illegal state transition | 409 |
| Concurrent write, no precondition sent | 409 |
Key facts
- A 412 means a precondition the client sent is no longer true, which makes it machine-actionable: re-read, merge, retry with the new version.
- A 409 means the request conflicts with current state for a reason the client did not test, so the response body is required to know what happened.
- A 428 Precondition Required means the server refuses unconditional writes, forcing every client into optimistic locking.
- Removing the precondition header to turn a 412 into a success converts a visible conflict into a silent lost update.
- Using 409 where 412 applies costs clients the ability to retry automatically, because the reason is no longer in the status.
Frequently asked questions
If I only support one, which should it be?
Support 412 with If-Match. It is the one that lets clients recover without bespoke code, and it makes the concurrency model explicit rather than implied.
Is 409 wrong for a version conflict?
Not wrong, but weaker. If the client sent a precondition, 412 states precisely which one failed; 409 makes the client parse a body to learn the same thing.
What should a 409 body contain?
A stable code the client can branch on, and the identity of whatever conflicted. A message alone is not enough, because the wording changes and clients end up matching on strings that were never meant to be an interface.
Machine-readable copy of this page:
/guide/when-you-get-412-and-when-409.md