Conditional Request Headers conditional request
Headers that make HTTP requests conditional — only proceed if the resource has (or hasn't) changed since you last saw it.
What conditional requests are
Conditional requests let clients attach a "precondition" to their request — a condition the server checks before processing. If the condition passes, the server proceeds normally. If it fails, the server returns a short response without the full body, saving bandwidth and preventing unintended operations.
They power two major use cases:
- Cache validation — "give me this resource, but only if it changed since I last fetched it"
- Optimistic concurrency control — "update this resource, but only if nobody changed it since I read it"
All conditional headers are evaluated against either an ETag (strong identity) or Last-Modified (timestamp). ETags take precedence when both are present.
The 5 conditional headers
If-None-Match
Used for: cache validation on GET/HEAD
Sends the client's cached ETag. The server compares it against the current ETag:
- Match (resource unchanged) →
304 Not Modified, no body. Client uses cached copy. - No match (resource changed) →
200 OKwith fresh content and new ETag.
GET /api/users/42 HTTP/1.1
If-None-Match: "v7-abc123"
# Resource unchanged:
HTTP/1.1 304 Not Modified
ETag: "v7-abc123"
# Resource changed:
HTTP/1.1 200 OK
ETag: "v8-def456"
{"id": 42, "name": "Sainesh", ...}
The name is confusing: "None-Match" means "return the content if the ETag does NOT match" — i.e., if the resource is different from what I have cached.
If-None-Match: * means "return the content only if this resource doesn't exist at all" — useful for PUT-to-create that should fail if the resource already exists.
If-Match
Used for: optimistic concurrency control on PUT/DELETE/PATCH
Sends the ETag the client read when it fetched the resource. The server only applies the change if the current ETag still matches — guaranteeing nobody else changed it in the meantime:
- Match → proceed with the update. Return
200or204. - No match (someone else changed it) →
412 Precondition Failed. Client must re-fetch before retrying.
PUT /api/documents/99 HTTP/1.1
If-Match: "v3-xyz789"
Content-Type: application/json
{"title": "Updated Title", "content": "..."}
# ETag matches — update succeeds:
HTTP/1.1 200 OK
ETag: "v4-abc123"
# ETag mismatch — someone else updated it:
HTTP/1.1 412 Precondition Failed
This prevents the "lost update" problem: two users fetch the same document, both edit it, and the second save silently overwrites the first. With If-Match, the second save fails with 412 — the user is told the document changed and must review the new version.
If-Match: * means "only proceed if this resource exists" — useful for PUT-to-update that should fail if creating a new resource.
If-Modified-Since
Used for: cache validation on GET/HEAD (timestamp-based)
The timestamp equivalent of If-None-Match. Sends the Last-Modified value from the cached response:
- Not modified since that time →
304 Not Modified - Modified after that time →
200 OKwith fresh content
GET /api/articles/1 HTTP/1.1
If-Modified-Since: Mon, 10 Jun 2026 08:00:00 GMT
# Not modified:
HTTP/1.1 304 Not Modified
Last-Modified: Mon, 10 Jun 2026 08:00:00 GMT
# Modified:
HTTP/1.1 200 OK
Last-Modified: Mon, 22 Jun 2026 14:30:00 GMT
{"id": 1, "title": "Updated Article", ...}
Limitation: 1-second granularity. If a resource changes twice within the same second, the second change is invisible to If-Modified-Since. Use If-None-Match with ETags when sub-second accuracy matters.
If-Unmodified-Since
Used for: optimistic concurrency control (timestamp-based)
The timestamp equivalent of If-Match. The server only proceeds if the resource has NOT been modified since the given time:
- Not modified → proceed with the operation
- Modified after that time →
412 Precondition Failed
DELETE /api/articles/1 HTTP/1.1
If-Unmodified-Since: Mon, 10 Jun 2026 08:00:00 GMT
# Resource unchanged since then — delete proceeds:
HTTP/1.1 204 No Content
# Resource was modified — reject:
HTTP/1.1 412 Precondition Failed
Prefer If-Match with ETags over If-Unmodified-Since — ETags are more precise (no 1-second granularity issue) and don't depend on clock synchronisation.
If-Range
Used for: safe range request resumption
Combines range requests with conditional logic. Used when resuming a partial download — if the resource hasn't changed, resume from where you left off. If it has changed, start over with the full resource.
GET /downloads/large-file.zip HTTP/1.1
Range: bytes=52428800-
If-Range: "etag-of-original-download"
# ETag matches — resource unchanged, continue:
HTTP/1.1 206 Partial Content
Content-Range: bytes 52428800-104857599/104857600
# ETag mismatch — resource changed, full download:
HTTP/1.1 200 OK
<entire resource>
Without If-Range, you'd have to check the ETag manually before sending the Range header — two round trips. If-Range does it in one.
Accepts either an ETag or an HTTP date as its value.
The full evaluation hierarchy
When a server receives a request with multiple conditional headers, it evaluates them in this order (per RFC 9110):
If-MatchIf-Unmodified-SinceIf-None-MatchIf-Modified-SinceIf-Range(only when Range is present)
ETags always take priority over dates when both are present.
Which header to use when
| Use case | Header |
|---|---|
| Cache validation (browser, API client) | If-None-Match (ETag) or If-Modified-Since (timestamp) |
| Prevent lost updates (PUT/PATCH) | If-Match |
| Prevent creating duplicate (PUT-to-create) | If-Match: * |
| Ensure resource exists before delete | If-Match: * |
| Safe partial download resumption | If-Range |
| Legacy cache validation (no ETag) | If-Modified-Since |
Common mistakes and gotchas
Using If-Modified-Since when ETags are available. ETags are always more accurate. Sub-second changes, files that change in ways that don't update mtime (filesystem copies), and clock drift all cause If-Modified-Since to fail silently. Use ETags.
Not implementing If-Match on mutation endpoints. REST APIs that don't require If-Match on PUT/PATCH/DELETE are vulnerable to lost updates. It's easy to skip in initial implementation and painful to add later once clients assume they can overwrite without preconditions.
Returning 200 instead of 304 when nothing changed. If the client sends If-None-Match and the resource hasn't changed, return 304 Not Modified, not 200 OK. Returning 200 wastes bandwidth and tells the client its cache is wrong when it isn't.
Ignoring conditional headers entirely. Many frameworks don't implement conditional request handling automatically. If your framework doesn't do it, you have to implement it yourself for cached endpoints. Ignoring If-None-Match means every request re-downloads the full response.
Real-world examples
Efficient API polling with If-None-Match:
GET /api/notifications HTTP/1.1
If-None-Match: "hash-of-last-response"
# Nothing new:
HTTP/1.1 304 Not Modified ← no body, just headers
# New notifications:
HTTP/1.1 200 OK
ETag: "new-hash"
[{"id": 99, "message": "New comment on your post"}]
Safe document edit with If-Match:
# User A fetches:
GET /docs/42 → ETag: "v5"
# User B also fetches:
GET /docs/42 → ETag: "v5"
# User A saves:
PUT /docs/42 with If-Match: "v5" → 200 OK, ETag: "v6"
# User B tries to save:
PUT /docs/42 with If-Match: "v5" → 412 Precondition Failed
# User B must re-fetch "v6" before editing
FAQ
What's the difference between If-None-Match and If-Match?
They're opposites in intent. If-None-Match says "proceed if the current ETag does NOT match what I have" — used to detect changes for cache revalidation (give me fresh content if it changed). If-Match says "proceed only if the current ETag DOES match what I have" — used to prevent lost updates (don't apply my change if someone else already changed it).
Can I send multiple ETags in If-None-Match?
Yes — If-None-Match accepts a comma-separated list: If-None-Match: "v1", "v2", "v3". The server returns 304 if the current ETag matches any of the listed values. Useful in edge cases where a resource might have multiple valid cached versions.
Do browsers use these headers automatically?
Yes. When a cached response has an ETag, the browser automatically sends If-None-Match on the next request for that resource (after cache expiry or on reload). Same for If-Modified-Since with Last-Modified. You don't need to set these manually in browser code — the browser handles it transparently.
Fun fact
Conditional requests are HTTP's implementation of optimistic concurrency control — a pattern from database theory where you assume conflicts are rare and detect them at write time rather than locking at read time. The alternative (pessimistic locking — lock the resource when reading so nobody else can edit it) was considered for HTTP but rejected because HTTP is stateless and a locked resource would stay locked if the client disconnected. Optimistic concurrency via ETags and If-Match is a stateless, distributed-friendly solution to a problem that has existed since the first collaborative document systems were built.