Back to HTTP Headers

If-Match conditional request

Makes a request conditional on an ETag matching exactly — used to prevent lost updates when multiple clients edit the same resource.

What it does

If-Match makes a request conditional on the resource's current ETag matching exactly what the client supplies. It's the standard mechanism for optimistic concurrency control over HTTP — protecting against the "lost update" problem, where two clients read the same resource, both make edits, and the second write silently overwrites the first without either client knowing a conflict happened.

Instead of locking the resource pessimistically (which doesn't scale well over stateless HTTP), the client sends back the ETag it saw when it last read the resource. If the resource has been modified by anyone else since then (different ETag now), the server rejects the write with 412 Precondition Failed — forcing the client to re-fetch the latest version and either merge changes or explicitly overwrite, rather than blindly clobbering someone else's edit.

Syntax

If-Match: "<etag-value>"
If-Match: "<etag-value>", "<etag-value>"
If-Match: *

Can list multiple ETags (comma-separated) — the condition passes if any of them match. * means "any current representation exists" — useful for requiring a resource to exist without caring what its specific ETag is.

Unlike If-None-Match, this uses strong comparison only — a weak ETag (W/"...".) never satisfies If-Match, even against an identical weak ETag on the server side. This is deliberate: write safety requires certainty the underlying bytes are unchanged, not just "close enough."

Preventing the lost update problem

GET /documents/42 HTTP/1.1

HTTP/1.1 200 OK
ETag: "v1-abc123"
Content-Type: application/json

{"title": "Draft", "content": "..."}

Client edits locally, then submits the update with the ETag it read:

PUT /documents/42 HTTP/1.1
If-Match: "v1-abc123"
Content-Type: application/json

{"title": "Final", "content": "..."}

If unchanged since the read — write succeeds:

HTTP/1.1 200 OK
ETag: "v2-def456"

If someone else already modified it — write is rejected:

HTTP/1.1 412 Precondition Failed

The client sees 412, knows its view was stale, and can re-fetch the current version to decide how to proceed — merge, prompt the user to resolve a conflict, or explicitly overwrite if that's the intended behavior.

Common mistakes and gotchas

Confusing this with If-None-Match. If-Match proceeds on a match (protects updates from clobbering concurrent changes). If-None-Match proceeds on a non-match (drives cache revalidation, or checks for non-existence with *). These are opposite semantics — using the wrong one silently defeats either your concurrency control or your caching.

Skipping conditional requests on PUT/PATCH/DELETE entirely. Many APIs implement ETag generation on GET responses but never actually check If-Match on writes, meaning the ETags are decorative — clients can read them, but there's no enforcement preventing lost updates. If concurrent editing is a real scenario for your resource, the write side needs to actually validate the header, not just the read side generate it.

Using weak ETags where If-Match is required. Since If-Match mandates strong comparison, if your ETags are generated as weak (W/"...".) for caching-friendliness elsewhere, they'll never satisfy If-Match checks — you may need separate strong validators for concurrency-sensitive write operations, or ensure your ETag generation strategy produces strong ETags when this matters.

Assuming If-Match: * means "always allow." It means the opposite of what people sometimes expect at a glance — it requires that some current representation exists (so it fails on a resource that doesn't exist yet), which is the reverse of If-None-Match: *'s "create only if absent" pattern. Easy to swap these mentally.

Real-world examples

Successful conditional update:

PATCH /api/inventory/sku-1029 HTTP/1.1
If-Match: "rev-88"
Content-Type: application/json

{"quantity": 45}

HTTP/1.1 200 OK
ETag: "rev-89"

Rejected due to concurrent modification:

PATCH /api/inventory/sku-1029 HTTP/1.1
If-Match: "rev-88"
Content-Type: application/json

{"quantity": 45}

HTTP/1.1 412 Precondition Failed

Someone else already updated this to rev-89 (or later) since the client last read it — the client must re-fetch and retry with the current ETag.

Requiring the resource to exist:

DELETE /api/sessions/abc123 HTTP/1.1
If-Match: *

HTTP/1.1 404 Not Found

The session was already deleted or never existed — If-Match: * fails here since there's nothing to match against.

FAQ

What HTTP status code does a failed If-Match check return?

412 Precondition Failed — the request is rejected before any modification is applied, and the response body typically doesn't include the current resource state (unlike If-None-Match's 304, which does return the current ETag).

Can If-Match use weak ETags?

No — If-Match requires strong comparison, meaning weak ETags never satisfy it, even if the underlying content would be considered equivalent. This is intentional: concurrency-safe writes need certainty of byte-identical state, not the looser "semantically equivalent" guarantee weak ETags provide.

Is If-Match the same as database optimistic locking?

Conceptually yes — it's the HTTP-level equivalent of the version-column pattern used in database optimistic concurrency control (check the version hasn't changed, update, increment the version). If-Match just does this over HTTP using the resource's ETag as the version token instead of a database column.

Do I need If-Match if only one client ever modifies a resource?

Not really — the lost update problem only arises with concurrent writers. For single-writer scenarios, If-Match adds unnecessary complexity. It becomes valuable specifically when multiple users, tabs, devices, or services might modify the same resource around the same time.

Fun fact

The lost update problem If-Match solves predates HTTP entirely — it's one of the classic concurrency control problems from database theory, and REST APIs essentially imported the optimistic-locking pattern from database systems into the stateless HTTP request/response model. The clever part is that ETag/If-Match let you get this safety property without any server-side session state or locks held between requests — the "lock" is really just a value the client carries and presents back, making it a genuinely stateless solution to what's traditionally a stateful problem.