Summary

  • HTTP 428 lets an origin refuse a state-changing request that lacks a required condition. It turns conditional writing from optional client caution into server-enforced admission policy.
  • The usual protection is If-Match with a strong entity tag: 428 means the predicate was missing, while 412 means a supplied predicate evaluated false.
  • The status does not lock, merge or transact. Safety still depends on useful validators, atomic comparison and mutation, and a client that reconciles current state instead of blindly refreshing a tag.

The quiet overwrite

Two editors open the same document. Both see version seven. One corrects a paragraph and saves version eight. The other, still looking at version seven, replaces the entire document with an edit that never contained the first correction. The server accepted two legitimate users, two valid payloads and two successful requests. Yet one piece of work vanished.

This is the lost-update problem in its plainest form. It does not require corrupted packets or an attacker. The error appears because the second write names a destination but not the state on which its decision was based. “PUT this document” is weaker than “PUT this document only if the current representation is still the one I read.”

HTTP had long possessed conditional request fields. A server could attach an entity tag to a representation, and a client could return that tag as a predicate. What the protocol lacked was an exact response for an origin that refused to accept the weaker, unconditional form at all.

In April 2012, RFC 6585 supplied it. 428 Precondition Required means the origin server requires the request to be conditional. Its typical use, the RFC says, is to avoid a client overwriting a state that somebody else changed after the client's read.

From a useful option to an admission rule

Conditional requests are often introduced through caching. A browser asks whether its stored copy remains current; a matching validator can produce 304 and avoid sending the body again. That history can obscure a more consequential use of the same evidence.

For a write, a validator is not merely a bandwidth hint. It is the writer's claim about the past. With If-Match: "v7", the client tells the origin to perform the method only if the current selected representation still carries the strong tag "v7". If another write has advanced the tag to "v8", the condition is false and the requested mutation must not happen.

Before 428, a careful client could choose this discipline. After 428, an origin could make the discipline a resource policy and explain why an unconditional request had not crossed the gate. RFC 6585 even gives a response example that tells the client to try If-Match.

The example is guidance, not a universal law. Some operations assert existence with If-Match: *. A create operation can assert nonexistence with If-None-Match: *. Where an entity tag is unavailable, If-Unmodified-Since can express a time-based condition. The origin must say which evidence its operation accepts. The durable innovation is not one header spelling; it is the origin's right to require a predicate before action.

Missing evidence is not failed evidence

HTTP's neighbouring status codes preserve a valuable distinction.

428 Precondition Required answers an admission question: did the request bring an acceptable condition when this resource policy requires one? If not, the origin refuses before attempting the state-changing method.

412 Precondition Failed answers an evaluation question: the request did bring a predicate, but the origin compared it with current state and found it false. The client named its past; that past no longer matches the present.

Collapsing the two responses would erase operational information. A fleet producing many 428 responses has clients that do not know or do not follow the concurrency contract. A fleet producing many 412 responses may have stale sessions, active simultaneous editing or a validator that changes too often. The remedies differ.

Nor is 428 the same as 409 Conflict. A 409 can describe a semantic conflict between a request and current resource state. A 428 policy asks for the evidence before the origin risks discovering the conflict through an overwrite. And unlike WebDAV's 423, it does not report or create a lock.

A strong tag is deliberately strict

The current HTTP semantics in RFC 9110 require strong comparison for If-Match. Weak tags are useful when two representations are equivalent enough for cache reuse. They are not exact enough for a writer who intends to stop the method whenever representation data has changed.

This difference turns validator generation into part of the write API. A tag that fails to change when protected state changes can admit a stale update. A tag that changes for irrelevant formatting noise can reject harmless work and create a storm of false conflicts. A service that demands If-Match but never exposes a usable strong tag has built a dead end, not a recovery contract.

Existence predicates deserve the same care. If-Match: * says some current representation must exist. If-None-Match: * says none may exist, which can protect concurrent creation. The wildcard does not identify exact bytes, but it still makes the client's assumption explicit and testable.

Dates are less exact. If-Unmodified-Since can serve the lost-update purpose when no entity tag is available, but RFC 9110 treats If-Match as the more accurate replacement and ignores the date condition when both appear. Clock behavior and coarse timestamps make a date a different grade of proof.

The comparison belongs beside the commit

HTTP carefully places precondition evaluation at the origin, after ordinary request checks and immediately before the method's action. An intermediary must not decide whether If-Match is true for origin state it does not own. It forwards the request to the place that can answer.

That sequence matters inside the application too. Suppose a handler reads the current tag, declares the condition satisfied, releases its database transaction, and writes later. A competing change can enter between check and commit. The external HTTP exchange looks conditional, but the implementation has recreated the time-of-check/time-of-use race the condition was meant to close.

The safe boundary is atomic compare-and-act. The predicate and the mutation must meet in one authoritative decision. A database version column, compare-and-swap operation or equivalent transaction can supply that boundary. HTTP specifies the meaning visible on the wire; it cannot manufacture atomicity inside a service.

The protected state must also match the validator's scope. A tag for a rendered page may be a poor guard for several backend records. A request that changes an account and emits an external message crosses more than one state boundary. HTTP 428 cannot turn those pieces into one transaction.

Rejection is not reconciliation

A dangerous client response to 412 is to fetch a fresh tag and attach it to the same obsolete whole-document body. The new predicate will pass, but no human or merge algorithm has considered the intervening change. The client has converted a visible conflict into an authorized overwrite.

Correct recovery usually requires reacquiring current state, comparing it with the user's pending intent and choosing whether to merge, abandon or replace. For some data, a field-level patch or a commutative operation makes that decision smaller. For other data, only a person can resolve the disagreement. 428 exposes the need for this work; it does not perform it.

The status also does not promise that every client will receive this protection. RFC 6585 makes its additional codes optional and explicitly warns that clients cannot rely on 428 to prevent lost updates. A client that cares about its own update must still use the appropriate condition when the API offers one, rather than waiting for the origin to complain.

The response cannot become stored policy

RFC 6585 says a 428 response must not be stored by a cache. The refusal describes the admission requirements of one attempted operation, not a reusable representation of the target. A cache serving an old 428 later could block a request after policy or state had changed and would put an intermediary in the role of inventing origin authority.

The response should instead explain how to resubmit successfully. “Precondition Required” alone is insufficient for an unfamiliar client. A useful API exposes the current validator, documents the supported field and makes clear whether the client must read, reconcile and retry.

The IANA HTTP Status Code Registry still records 428 under RFC 6585. Its compact name conceals a large design decision. The Internet did not need another word for “error”. It needed a word for a write that had failed to disclose its basis.

Sources and evidence limits

The evidence is RFC 6585, the current HTTP semantics in RFC 9110, and the IANA registry. They establish the status, conditional-field behavior, evaluation order and explicit limitations. They do not establish current deployment share, universal library behavior, a named incident, measured conflict losses or exactly-once execution. 428 is not a lock, transaction or merge protocol.