How to resolve a write conflict without losing data
Your write was rejected with 409 or 412 because someone else changed the resource since you read it. Retrying blindly would overwrite their change.
Re-read the current version, merge your change into it, and retry with the new version marker. Never refetch and overwrite: that reproduces exactly the bug the conflict check exists to prevent, discarding the other writer's work with no error raised anywhere.
What the rejection is telling you
Optimistic locking lets everyone write and checks at commit time whether the underlying version still matches what the writer read. A rejection means the resource moved on. That is information, not a failure — the system just prevented a lost update.
The wrong fix, which looks right
The tempting response is to fetch the latest version and resend your payload with the new marker. It succeeds, and it silently destroys whatever the other writer did. Nothing errors, no log line appears, and the loss is discovered later by someone whose edit vanished.
Merging when the payload is a document
For structured documents, merge field by field and keep both sides where they do not overlap. For free text, a three-way merge against the common ancestor is the honest approach; where that is impossible, surfacing the conflict to a human beats picking a winner silently.
When to stop retrying
Under heavy contention a merge loop can spin. Bound it, and when the bound is hit, escalate rather than force. Repeated conflicts on the same resource usually mean optimistic locking is the wrong choice there and a real lock would be simpler.
Conflict responses and what they mean
Conflict responses and what they mean
| Status | Meaning | Correct response |
| 409 Conflict | The request conflicts with current state | Re-read, merge, retry |
| 412 Precondition Failed | If-Match did not match the current version | Re-read, merge, retry with new ETag |
| 428 Precondition Required | The server demands a version marker | Read first, then write with If-Match |
| 200 after forcing | You removed the precondition | You have probably lost an update |
Key facts
- A 412 Precondition Failed means someone wrote first; the correct response is to re-read, merge and retry, never to force the write.
- Refetching the current version and resending your payload succeeds while silently discarding the other writer's change, which is the exact failure the check prevents.
- Optimistic locking suits low-contention data; under heavy contention the retry loop can cost more than a pessimistic lock.
- A version marker must change on every write, or the check passes when it should have failed.
- Dropping the precondition header to make a write succeed converts a visible conflict into an invisible data loss.
Frequently asked questions
Why not just always take the latest write?
Last-write-wins is a choice, not a default, and it should be made deliberately. It is defensible for a cache entry and indefensible for a shared document, where it means one person's work disappears without either of them being told.
How do I merge when I only have the new version, not the ancestor?
Keep the version you read. A two-way merge cannot tell an addition from a deletion, so without the ancestor you are guessing. Systems that expect merges usually hand you their copy alongside the rejection for this reason.
Is 409 or 412 the right status for a version mismatch?
Use 412 when the client sent a precondition such as If-Match, because that is precisely what failed. Use 409 when the conflict is semantic and no precondition was involved.
Machine-readable copy of this page:
/guide/resolve-write-conflicts.md