HTTP If-Match: Prevent Lost Updates in Your API

Prevent lost API updates with strong ETags, If-Match checks and atomic version comparisons, then give clients a safe conflict-resolution workflow.

In this article

HTTP If-Match: Prevent Lost Updates in Your API

Two users can open the same record, make different edits and accidentally overwrite each other. A successful response to both requests does not mean both changes survived. Use a conditional write to reject an update based on an outdated version before it replaces newer work.

HTTP's If-Match header provides a standard way to express that precondition. The client sends the version it edited; the server applies the write only if that version still matches. The database check must be atomic, otherwise the API can still lose an update between checking and writing.

Show the race with a small example

Suppose an article begins with title A and excerpt B. Two editors load the same version. One changes the title, while the other changes the excerpt in an older full document. If the second document is saved blindly, it can restore the old title.

Create this race in a staging environment with two independent clients. Use a synthetic article and record the sequence. This is more informative than testing concurrent requests whose bodies are identical.

Use JSON Diff to compare the initial document, the first saved document and the stale second document. The comparison reveals which field would be lost.

Return a version with the resource

An API can expose a strong ETag representing the selected resource version. An illustrative response includes:

http
ETag: "article-42-v7"

The client preserves that exact quoted value alongside its editing state. It then sends:

http
If-Match: "article-42-v7"

The conditional-request guide explains that If-Match uses strong comparison and can prevent lost updates. A failed precondition is represented by HTTP 412. This use is about write correctness, even though ETags also appear in caching workflows.

Make the comparison part of the write

Don't read the current version, compare it in application code and then issue an unconditional update. Another writer could change the resource between those steps.

Instead, include the expected version in the database update condition. A simplified SQL shape is:

sql
UPDATE articles
SET title = ?, version = version + 1
WHERE id = ? AND version = ?;

Check the affected row count. If no row changed, determine whether the resource is missing or the version precondition failed without exposing another tenant's records. Use the SQL Formatter to review a synthetic statement, but implement parameter binding and authorization in the server.

The version should advance for every relevant modification path, including administrative scripts and background jobs. A write that bypasses version advancement defeats the client's conflict protection.

Decide what to do when the header is absent

For endpoints where lost updates are unacceptable, require a precondition. Document the response clients receive when they omit it. A common design uses HTTP 428 to indicate that a precondition is required, while a supplied but outdated precondition produces 412.

If you retain unconditional writes for compatibility, make the distinction explicit. Otherwise, old clients can continue overwriting protected edits and the new interface creates a false sense of safety.

Keep authorization separate from the version check. A correct ETag is not permission to modify a resource. Verify the current actor, tenant and allowed fields before committing the write.

Give the client a conflict workflow

When a precondition fails, preserve the user's draft. Fetch the current authorized resource and show what changed. Offer a merge or a deliberate overwrite workflow according to the product's needs.

Never automatically fetch a new ETag and resend the stale full document. That removes the protection and turns a detected conflict into an intentional-looking overwrite. The client needs to reconsider its intended changes against the latest state.

For field-level updates, compare the original base, current server document and local edits. A three-way comparison can distinguish independent changes from two edits to the same field. Treat array order and deletions carefully.

Test conflicts and retries separately

A repeated request after a lost response is a retry problem. A stale version after another writer succeeds is a concurrency problem. They can occur together, but one mechanism doesn't automatically solve the other.

Test an accepted update, a stale update, a missing precondition, a deleted resource and a retry after an uncertain response. Confirm that failed preconditions leave the resource unchanged.

For patch endpoints, define how the conditional version interacts with patch application. The server should apply the patch to the version that passed the check, within the same protected operation. See JSON Patch versus Merge Patch for the update-document semantics.

Inspect headers at the right boundary

The HTTP Header Checker can inspect public response headers. It cannot submit authenticated conditional writes or reproduce a private editing session. Use your integration test client for the actual write sequence.

Check whether gateways preserve the ETag and If-Match header and whether CORS exposure allows the frontend to read the response value. Log conflict counts and resource types without logging private document bodies.

Conclusion

Prevent lost updates by returning a strong version identifier, requiring clients to send it and comparing it atomically during the write. Preserve drafts after a conflict and merge against current state. Conditional requests are effective when every writer participates in the same versioning contract.

Advertisement
HTTP If-Match: Prevent Lost Updates in Your API | Duck Cloud