An ETag and a Last-Modified date solve the same broad problem: they let a client ask whether a representation has changed before downloading it again. They are not interchangeable, though. An ETag is an opaque identifier chosen by the server. Last-Modified is a timestamp with one-second precision. That difference affects cache revalidation, concurrent updates and the failure modes your API has to handle.
The usual default is simple: emit both validators when you can do so correctly, prefer ETags for exact representation identity, and use Last-Modified as a useful time-based fallback.
The decision in one table
| Need | Prefer | Why |
|---|---|---|
| Revalidate a cached response | ETag, often with Last-Modified too | An entity tag can change whenever the selected representation changes |
| Cheap validation from an existing update timestamp | Last-Modified | It may require no new hashing or version column |
| Protect an update from overwriting a newer version | Strong ETag with If-Match | The request succeeds only when the current representation still matches |
| Avoid creating a resource that already exists | If-None-Match: * | The wildcard makes creation conditional on absence |
| Support older or simpler caches | Both validators | RFC 9110 says servers should send both when feasible |
A validator is useful only when its generation rule matches what clients are validating. A tag derived from the wrong state can be worse than no tag because it makes stale content look current.
ETag: a representation identifier
The ETag response field carries an entity tag. Its value is opaque to the client: "a1b2c3" does not need to reveal a hash, row version or build number. The server only needs to apply the comparison rules consistently.
Strong and weak tags have different jobs:
- A strong ETag, such as
"v17", means the representations are equivalent at the byte-level standard required for range requests andIf-Match. - A weak ETag, written with the
W/prefix, can mean two representations are semantically equivalent even if their bytes differ. Weak comparison is enough for ordinary cache revalidation withIf-None-Match. - Weak tags never satisfy
If-Match, because that header uses strong comparison.
The tag does not have to be a content hash. A database row version, immutable object version, or build identifier can be faster and safer. Hashing a large response on every request may cost more than the transfer it saves. Whichever method you choose must account for content negotiation: compressed and uncompressed responses, languages, or media types may need distinct tags when their selected representations differ.
Last-Modified: a timestamp validator
Last-Modified records when the origin believes the selected representation last changed. A client can send that value back in If-Modified-Since. If the resource has not changed since the supplied date, a GET or HEAD request can return 304 Not Modified.
This validator is convenient when the source already has a trustworthy modification time. It also has limits:
- HTTP dates only resolve to whole seconds.
- Two changes inside the same second can share a timestamp.
- Clock skew and reconstructed data can produce misleading dates.
- A resource assembled from several records needs a timestamp that reflects every relevant dependency.
That makes Last-Modified a good fallback, but not a reliable optimistic-lock token for many write-heavy APIs.
What conditional GET actually sends
A cache stores the response together with its validator. When the cached response becomes stale, it can ask the origin to revalidate it:
GET /reports/quarterly HTTP/1.1
If-None-Match: "report-42"
If-Modified-Since: Wed, 21 Oct 2026 08:00:00 GMT
If the selected representation still matches, the server answers with 304 Not Modified and no message body. The response still needs the metadata that a cache would use to update the stored response, including fields such as ETag, Date, Vary and cache controls when they would have appeared in a 200 response.
If the representation changed, the server returns a normal 200 OK response with the new body and validators.
When both conditions are present, If-None-Match takes precedence over If-Modified-Since. The date condition is ignored when the recipient supports the entity-tag condition. Sending both is still useful because it gives older intermediaries a fallback.
Freshness and validation are separate controls: cache lifetime decides when a stored response becomes stale, while the validator decides whether that stale copy can be reused.
Conditional writes need a strong validator
The most important difference appears on writes. Suppose two editors retrieve the same document. One saves first. Without a precondition, the second save can silently overwrite the first.
A client can instead send the strong ETag it originally received:
PUT /documents/17 HTTP/1.1
If-Match: "revision-8"
Content-Type: application/json
The server applies the update only if the current representation still has "revision-8". If someone already changed it, the precondition fails and the server returns 412 Precondition Failed. The client can fetch the new state, reconcile the conflict and try again.
This is optimistic concurrency control. It is related to retry safety but not the same thing. An idempotency key prevents one logical operation from being applied twice; If-Match prevents an operation from being applied to a state the caller did not read. Webtamine’s idempotency-key guide explains that boundary.
If-None-Match is more than a cache header
For GET and HEAD, If-None-Match is the common ETag revalidation condition. With other methods it can also prevent an accidental overwrite. The wildcard form is especially useful:
PUT /users/alex HTTP/1.1
If-None-Match: *
The request proceeds only if no current representation exists at that URI. This supports create-only semantics without a separate read-then-write race.
Do not implement this as “check, then write” in two independent database operations. The condition and mutation must be evaluated atomically or another request can slip between them.
Generation strategies and their trade-offs
There is no universally correct ETag algorithm. Common approaches include:
- Content hash. Accurate when calculated over the exact selected representation, but potentially expensive. Be careful about compression and serialization differences.
- Database version column. Cheap and suitable for concurrency control when every relevant change increments the version.
- Immutable object version. Natural for files or blobs stored with version identifiers.
- Composite version. Necessary when one response depends on several rows or services. The tag must change when any visible component changes.
Last-Modified usually comes from a stored update time or the latest modification among dependencies. If you cannot calculate either validator faithfully, do not fabricate precision. Cache-Control directives can still govern freshness without a validator.
Common implementation mistakes
Reusing one ETag across negotiated variants
If the response changes with Accept-Encoding, Accept-Language or another request field, the tag must describe the selected representation under the server’s comparison model. Keep Vary aligned with that selection.
Treating a weak tag as a write lock
A weak ETag cannot satisfy If-Match. Use a strong validator for lost-update protection.
Giving precedence to the date
When a supported request contains both If-None-Match and If-Modified-Since, evaluate the entity-tag condition first. Do not let a coarse timestamp override a more specific tag.
Returning a body with 304
A 304 response is not a compact 200 response. It has no response content. Send the headers needed to update the stored response, not the representation body.
Using Last-Modified from only one dependency
A rendered page or API response can depend on templates, permissions, prices or other records. A timestamp that ignores those inputs can incorrectly validate stale output.
A practical default
For static assets and ordinary GET endpoints, emit both ETag and Last-Modified when they are inexpensive and correct. Let If-None-Match drive revalidation, with If-Modified-Since as the fallback.
For mutable API resources, expose a strong ETag tied to the state clients are allowed to update, and require If-Match on conflict-sensitive writes. Keep idempotency keys for duplicate-operation protection. Those mechanisms solve different problems.
Document the rule. Clients need to know whether tags are strong or weak, whether variants receive distinct tags, and which update methods require a precondition. A validator is a protocol contract, not merely a performance header.
Common questions
Should every response have an ETag?
No. Generate one when you can make it change reliably with the selected representation. A misleading tag is worse than omitting the validator.
Is Last-Modified enough for browser caching?
It can support revalidation, but its one-second granularity and clock assumptions make it less precise than an entity tag. Many servers provide both.
Does a 304 response include the resource body?
No. A 304 response has no response content. The client reuses its stored representation.
Can I use a weak ETag with If-Match?
No. If-Match uses strong comparison, so a weak tag cannot match.
Sources and check date
Documentation checked October 23, 2026.





