Two clients can read the same HTTP resource, compute different replacements, and send those replacements minutes apart. If the origin accepts both writes without a precondition, the later request can overwrite the earlier result even though it was computed from stale state.

HTTP provides a protocol-level guard for this case. A client can retain an entity tag from the representation it read and send that tag in If-Match with a later state-changing request. The origin evaluates the precondition before applying the method. If no listed tag strongly matches the current selected representation, the method is not performed because of that precondition.

The validator carries a version assertion

An ETag identifies a selected representation version according to the origin server’s validator semantics. For concurrency control, the useful statement is not that the tag contains a hash, timestamp, or database revision. Its internal construction can remain opaque to the client.

The relevant property is the server’s mapping between representation changes and validator changes. If two states that must be distinguished for safe replacement can receive the same strong entity tag, the precondition cannot separate them.

A typical exchange starts with a read:

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

A later replacement can carry the observed validator:

PUT /profiles/17 HTTP/1.1
If-Match: "rev-41"
Content-Type: application/json

If the current selected representation still has "rev-41", the condition can succeed. If another accepted write has moved it to "rev-42", the stale request no longer satisfies the condition.

The tag is therefore a compact assertion about the state against which the client prepared its mutation.

Strong comparison is part of the concurrency contract

If-Match uses the strong entity-tag comparison function. A weak tag, marked with the W/ prefix, cannot satisfy this comparison.

That rule matches the purpose of the precondition. Weak validators can treat two representations as equivalent for uses where byte-level differences are not significant. A state-changing request that intends to avoid overwriting an intervening representation needs a stricter boundary.

This does not require every application to expose a content hash. A server can issue opaque strong tags backed by a revision counter or another mechanism, provided its validator semantics satisfy HTTP requirements and distinguish the representation changes relevant to the operation.

The strength marker is consequently not decorative metadata. It changes whether a validator is eligible to authorize an If-Match request.

A failed precondition stops the requested method

When an origin evaluates If-Match and the condition is false, HTTP semantics require it not to perform the requested method on that basis. The normal response for an unmet state-changing precondition is 412 Precondition Failed.

That result differs from accepting the write and reporting a conflict afterward. The precondition is evaluated before method execution, so the stale client does not first replace the current representation and then attempt to repair the collision.

The client can react by fetching current state, abandoning its pending change, or computing a new mutation against the newer representation. HTTP does not prescribe a universal merge policy. It supplies the conditional boundary; application semantics determine what a caller does after rejection.

This separation is important for APIs whose payloads are complete replacements. A stale PUT body may omit a field another actor just changed. Without a version precondition, a syntactically valid request can still erase that newer value.

Conditional writes require atomic server-side evaluation

A correct protocol exchange is not enough if the server implements the check and mutation as two unprotected operations.

Consider an application that reads the current revision from a database, compares it with the request tag in application code, then issues an unconditional update. Another transaction can modify the row between the comparison and the update. Both callers can pass the application-level check against the same old revision.

The storage boundary must make the precondition and mutation act as one concurrency decision. A relational implementation can, for example, encode the expected revision in the update predicate:

UPDATE profiles
SET body = :body, revision = revision + 1
WHERE id = :id AND revision = :expected_revision;

If the statement affects zero rows because the revision changed, the HTTP layer can map that failed condition to its precondition response. The exact mechanism differs across storage systems, but the invariant is stable: no competing write may slip between validation and mutation.

HTTP defines request semantics. It does not automatically make a multi-step backend implementation atomic.

If-Match star expresses existence, not a specific version

If-Match: * has different semantics from sending a particular entity tag. The condition is true when the origin has a current representation for the target resource and false when it does not.

That makes the wildcard useful for operations that must apply only to an existing resource. It does not assert that the resource is still at the version previously observed by the client.

A client protecting an edit against intervening changes therefore needs the relevant entity tag rather than the wildcard. Both forms are conditional, but they guard different facts.

The inverse existence condition also appears with If-None-Match: *: for a state-changing method, it can prevent creation from overwriting a resource that has appeared at the target URI. Create-only and update-if-current are separate concurrency contracts.

Entity tags also participate in cache revalidation. A cache can send If-None-Match to ask whether its stored representation still differs from the current one. For GET and HEAD, a matching condition can lead to 304 Not Modified rather than retransmission of the representation body.

That mechanism should not be collapsed conceptually with If-Match. If-None-Match asks for method execution only when the current tag does not match; If-Match asks for execution only when it does match.

The comparison rules also differ. If-Match requires strong comparison, while If-None-Match uses weak comparison. Reusing the same entity-tag vocabulary does not make the preconditions interchangeable.

An API can support both roles on the same resource: cache validators for efficient reads and strong validators for guarded writes.

Representation selection defines the validator boundary

An entity tag applies to a selected representation, not automatically to every piece of application state associated with a URI.

If response representation varies with content negotiation, authorization, transformation, or other selection logic, the origin needs validator behavior consistent with those representations. A client cannot safely infer that a tag obtained for one selected representation authorizes replacement under a materially different state model.

This becomes especially important when an API exposes derived views. A tag for a rendered projection may change when rendering inputs change, while the write endpoint may operate on a different underlying object. Using one validator across both surfaces is safe only when the server defines a coherent concurrency relationship between them.

The protocol carries the tag; the application still defines what state transition that tag protects.

Intermediaries do not remove origin responsibility

Conditional mutation semantics terminate at the origin server that owns the target state. A reverse proxy can forward If-Match, but correct concurrency control still depends on the component that can compare the validator with authoritative current state and apply the mutation atomically.

An intermediary that strips a conditional header changes the request semantics. An origin that ignores a required precondition likewise removes the collision guard. API gateways and application servers therefore need to preserve the fields that form the write contract.

Observability can distinguish these outcomes. A 412 indicates that a precondition was evaluated and failed under the server’s current state. An unconditional success after an intervening update points to a different path: no effective version guard protected that mutation.

The protection is scoped to the tagged resource transition

If-Match prevents a specific stale-write pattern when the validator faithfully represents the relevant version and the server evaluates the condition atomically with the mutation. It does not serialize arbitrary workflows across several resources.

A request that updates resource A and triggers asynchronous work on B has a broader consistency boundary than A’s entity tag alone. Likewise, two different endpoints that mutate the same underlying state need compatible validator semantics if callers expect one endpoint’s tag to guard writes through the other.

The mechanism is strongest when its scope is explicit: one conditional request, one current representation test, and one mutation whose application depends on that test.

Within that boundary, stale state becomes observable as a failed precondition instead of silently becoming the basis for a later overwrite.