Preconditions
Preconditions refer to the concept of a set of conditions that must be met before a particular operation (generally update of a resource) is performed.
For example, APIs often need to validate that a client and server agree on the current state of a resource before taking some kind of action on that resource. E.g., two processes updating the same resource in parallel could create a race condition, where the latter process “stomps over” the effort of the former one.
The ETag, If-Match, and If-None-Match headers provide a way to deal with this by allowing the server to send a checksum based on the current content of a resource; when the client sends that checksum back, the server can ensure that the checksums match before acting on the request.
Guidance
Section titled “Guidance”When adding precondition checking to an API (ETag, If-Match, and If-None-Match headers), the behavior must match preconditions what is documented in RFC 9110.
If a server receives a conditional header it does not support, the service should return a 400 Bad Request response. A server should support all preconditions or none of them.
ETag (entity tag) is a response header.
A resource may provide an ETag header when retrieving a single resource
when it is important to ensure that the client has an up-to-date resource
before acting on certain requests:
200 OKContent-type: application/jsonETag: "55cc0347-66fc-46c3-a26f-98a9a7d61d0e"The ETag must be provided by the server on output, and values should
conform to RFC 9110. Resources must support the If-Match header (and
may support the If-None-Match header) if and only if resources provide
the ETag.
ETags must be based on an opaque checksum or hash of the resource that guarantees it will change if the resource changes.
If-Match / If-None-Match
Section titled “If-Match / If-None-Match”If-Match and If-None-Match are a request headers.
Services that provide ETags should support the If-Match and
If-None-Match headers.
An example of using If-Match (the ETag returned from the request before is
in the If-Match header):
GET /v1/publishers/{publisherId}/books/{bookId}Accept: application/jsonIf-Match: "55cc0347-66fc-46c3-a26f-98a9a7d61d0e"If the service receives a request to modify a resource that includes an
If-Match header, the service must validate that the value matches the
current ETag. If the If-Match header value does not match the ETag, the
service must reply with 412 Precondition Failed.
If the user omits the If-Match header, the service should permit the
request. However, services with strong consistency
or parallelism requirements may require clients to send precondition
headers all the time and reject the request with a 428 Precondition Required
error if it does not contain an If-Match header.
If any conditional headers are supported for any operation within a service,
the same conditional headers must be supported for all mutation methods
(POST, PATCH, PUT, and DELETE) of any path that supports them, and
should be supported uniformly for all operations across the service.
If any validator or conditional headers are supported for any operations in the service, the use of unsupported conditional headers must result in a 400 Bad Request error response. (In other words, once a service gives the client reason to believe it understands conditional headers, it must not ever ignore them.)
Read requests
Section titled “Read requests”If a service receives a GET or HEAD request with an If-Match header, the
service must proceed with the request if the ETag matches, or send a 412
Precondition Failed error if the ETag does not match.
If a service receives a GET or HEAD request with an If-None-Match header,
the service must proceed with the request if the ETag does not match, or
return a 304 Not Modified response if the ETag does match.
Strong and weak ETags
Section titled “Strong and weak ETags”ETags can be either “strongly validated” or “weakly validated”:
- A strongly validated
ETagmeans that two resources bearing the sameETagare byte-for-byte identical. - A weakly validated
ETagmeans that two resources bearing the sameETagare equivalent, but may differ in ways that the service does not consider to be important.
Resources may use either strong or weak ETags, as it sees fit, but
should document the behavior. Additionally, weak ETags must have a W/
prefix as mandated by
RFC 9110 Etag Comparison.
200 OKContent-type: application/jsonETag: W/"55cc0347-66fc-46c3-a26f-98a9a7d61d0e"Strong ETags must, and weak ETags should, be guaranteed to change if any properties on the resource change that are directly mutable by the client. Additionally, strong ETags should be guaranteed to change if the resource’s representation changes in a meaningful way (meaning the new representation is not equivalent to the old one).
Concurrency control
Section titled “Concurrency control”Preconditions enable optimistic concurrency control, allowing multiple clients to work with the same resource without explicit locking.
When a service provides ETags, clients should use the If-Match header to
prevent concurrent modification conflicts. The typical flow is:
- The client retrieves the resource and receives an
ETag. - The client includes the
ETagvalue in theIf-Matchheader when making a mutation request. - The server validates that the
ETagmatches the current resource state. - If the
ETaghas changed, the server must return 412 Precondition Failed, indicating another client has modified the resource. - The client retrieves the updated resource, merges changes if needed, and
retries the operation with the new
ETag.
If no If-Match header is provided, the service should permit the request
using last-write-wins semantics. However, services with strong consistency or
parallelism requirements may require clients to always send ETags and
reject requests without them using 400 Bad Request.
Examples
Section titled “Examples”Example: Successful update with concurrency control
### Client retrieves the current resourceGET /books/123ETag: "v1"
{ "id": "123", "title": "Original Title", "author": "Jane Doe"}
### Client updates the resource with the ETagPUT /books/123If-Match: "v1"Content-Type: application/json
{ "id": "123", "title": "Updated Title", "author": "Jane Doe"}
### Server accepts the update200 OKETag: "v2"
{ "id": "123", "title": "Updated Title", "author": "Jane Doe"}Example: Concurrent modification conflict
### Client A retrieves the resourceGET /books/123ETag: "v1"
### Client B also retrieves the resourceGET /books/123ETag: "v1"
### Client A successfully updatesPUT /books/123If-Match: "v1"...200 OKETag: "v2"
### Client B attempts to update with stale ETagPUT /books/123If-Match: "v1"Content-Type: application/json
{ "id": "123", "title": "Different Title", "author": "Jane Doe"}
### Server rejects due to ETag mismatch412 Precondition Failed
{ "message": "The resource has been modified by another client. Please retrieve the latest version and retry." // ...rest of error...}Further Reading
Section titled “Further Reading”- HTTP Conditional Requests -
Covers date-based conditional headers like
If-Modified-SinceandIf-Unmodified-Since, which provide timestamp-based caching mechanisms complementary to ETag-based preconditions
Changelog
Section titled “Changelog”- 2026-02-24: Move concurrency to here from AEP-67.
- 2026-01-22: Initial creation, adapted from Google AIP-154 and aep.dev AEP-154.