Conditional requests with ETag
Conditional requests help avoid lost updates and save bandwidth. This is a practical RFC 9110-aligned recap of ETag-based flows.
Key headers
Pick the header by intent. ETags come in strong ("abc") and weak (W/"abc") forms.
Conditional headers
| Header | Purpose | If matched |
|---|---|---|
| If-None-Match | Cache revalidation. GET returns 304 on match. POST/PUT can return 412 to prevent overwriting existing resources. | GET: 304 / POST: 412 |
| If-Modified-Since | Timestamp-based revalidation. Fallback when ETag is not available. | GET: 304 |
| If-Match | Allow update/delete only when tags match. Great for optimistic locking. | PUT/PATCH/DELETE: 412 on mismatch |
| If-Unmodified-Since | Update/delete only if not modified since a timestamp. Alternative to If-Match. | PUT/PATCH/DELETE: 412 |
| If-Range | Resume partial GET. If tag/time mismatches, return 200 with the whole body. | GET (Range): 206 or 200 |
Common flows
Think in two buckets: cache saving and conflict avoidance.
Patterns
- Cache revalidation: send If-None-Match on GET; return 304 when tags match. Always emit ETag in responses.
- Create without overwriting: POST with If-None-Match: * and return 412 if it exists (RFC 9110 13.1.2).
- Optimistic locking: client GETs ETag, then sends If-Match with PUT/PATCH. If changed, return 412 to ask for a fresh read.
- Safe deletes: DELETE with If-Match to avoid removing an unexpected version. If already gone, many APIs still return 204 for idempotency.
- Partial resume: Range + If-Range to continue downloads; mismatch falls back to full 200.
Status codes
Match vs. mismatch decides the status.
Code mapping
| Situation | Return | Note |
|---|---|---|
| Match (If-None-Match / If-Modified-Since on GET) | 304 Not Modified | No body; still return ETag/Last-Modified. |
| Mismatch (If-Match / If-Unmodified-Since on writes) | 412 Precondition Failed | Tell the client to re-read. |
| Precondition header required but missing | 428 Precondition Required | Use when your API policy mandates If-Match. |
Design notes
Define how you mint ETags so the team can reason about cache and locking.
Tips
- Generate ETag from a hash or version that changes on any meaningful update.
- Use weak tags (W/) when semantically equivalent but byte-different; avoid them when strong comparison is required.
- If both If-Match and If-None-Match appear, evaluate If-Match first (RFC 9110 13.1.1).
- Even with 304, return cache headers (Cache-Control, Expires, Vary).
Takeaway
Conditional requests deliver two wins: bandwidth savings (304) and conflict prevention (412). With solid ETags, clients and servers stay in sync.