arthrop0d

Brief articles on varied topics.

HTTP ETags and If-None-Match Revalidation

web developmentnetworking

HTTP conditional requests let a client ask whether its cached response is still current. An ETag is a server-provided identifier for a particular representation, and If-None-Match is the request header a client sends with that identifier when it wants to revalidate its cache.

If the representation has not changed, the server returns 304 Not Modified without sending the representation again. If it has changed, the server returns 200 OK with the new representation. The distinction saves bandwidth while allowing the client to check freshness.

The problem: cached data can become stale

HTTP caching lets a browser, proxy, or other client reuse a previous response instead of contacting the origin server for every request. This is efficient, but a cached response has a limited lifetime or may need checking for other reasons.

Without conditional requests, the client has two poor choices:

ETag revalidation provides a third option: make a small request to ask whether the cached representation remains valid, then transfer the body only when necessary.

A representation is the response data being transferred, such as the JSON bytes for /api/products or the HTML document for /dashboard. An ETag identifies that representation in the context of a resource.

The basic exchange

Suppose a server first responds with a JSON document:

HTTP/1.1 200 OK
Content-Type: application/json
ETag: "products-7f31"
Cache-Control: max-age=60

{"items":[{"id":1,"name":"Keyboard"}]}

The client stores both the response body and its ETag. After the response is no longer considered fresh, the client can send a conditional request:

GET /api/products HTTP/1.1
Host: example.com
If-None-Match: "products-7f31"

The client is saying, in effect, “I have a representation tagged products-7f31; send me a new body only if that is no longer the current one.”

If the server determines that the current representation still matches that tag, it returns:

HTTP/1.1 304 Not Modified
ETag: "products-7f31"
Cache-Control: max-age=60

A 304 response does not contain the representation body. The client combines the 304 response's metadata with the body it already has and continues using its cached copy. Thus, the server has confirmed freshness while sending only response headers.

If the resource changed, the server instead sends the new representation normally:

HTTP/1.1 200 OK
Content-Type: application/json
ETag: "products-91aa"

{"items":[{"id":1,"name":"Keyboard"},{"id":2,"name":"Mouse"}]}

This 200 response includes the body because the client cannot use its old cached representation. It replaces the old cached response with the new one and its new ETag.

304 means “reuse the body you already have.” 200 means “here is the body you need.”

What the ETag represents

An ETag is an opaque value: clients compare it, but should not interpret its contents. A server might derive it from a content hash, a database version, or an internal revision number. The format is normally a quoted string, such as "products-7f31".

The server must generate tags consistently enough to identify whether the client’s stored representation is still usable. It does not have to expose how the tag was calculated.

ETags can be strong or weak. A strong ETag means the representations are byte-for-byte equivalent, which is useful when exact content matters. A weak ETag starts with W/, as in W/"products-7f31", and means the representations are equivalent for the purpose being checked even if their bytes differ in an insignificant way, such as formatting.

If-None-Match uses weak comparison: a weak and strong version of the same tag can match for cache validation. That makes it suitable for asking whether a cached response can be reused. Other operations that require exact byte identity may need stronger validation rules.

The header can also contain several tags if a client has alternatives, or * to mean “any current representation.” For ordinary cache revalidation, the client usually sends the single ETag stored with its cached response.

When revalidation happens

A cache does not need to revalidate every request. A response such as Cache-Control: max-age=60 can be reused for 60 seconds without contacting the server. Once that freshness period expires, the cache can send If-None-Match and wait for the origin's decision.

A cache may also revalidate because the user explicitly reloads a page, because configuration requires validation, or because another caching rule says the response must be checked. The exact decision belongs to the cache and its HTTP metadata; the ETag supplies the comparison value.

Conditional requests commonly apply to GET and HEAD, where the question is whether a representation needs to be transferred. For a GET, a successful match produces 304 Not Modified. The 304 response must not include a message body, and it is not an error or a redirect. It is a deliberately small success response.

In application logs, you may therefore see a sequence such as GET /api/products 304 after an earlier 200. That does not mean the client received an empty product list. It means the client already had the body and was told to keep using it. Conversely, a 200 after If-None-Match means the server sent a representation again—typically because its ETag no longer matched.

← All articles