Back to HTTP Headers

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 OK with 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 200 or 204.
  • 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 time304 Not Modified
  • Modified after that time200 OK with 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 time412 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):

  1. If-Match
  2. If-Unmodified-Since
  3. If-None-Match
  4. If-Modified-Since
  5. If-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.