# 304 Not Modified vs 200 OK

> Understand when servers return 304 Not Modified instead of 200 OK. Learn how conditional requests, ETags, and Last-Modified headers enable efficient HTTP caching.

Source: https://howhttpworks.com/compare/304-vs-200
Last reviewed: 2026-10-04

> **TL;DR:** Use 200 when the server needs to send the resource body. Use 304 when the client already has a cached copy and only needs confirmation that it is still current.

## The Fast Mental Model

If you are looking at DevTools and wondering why the browser got "no real response body," the answer is usually this:

- **200 OK** means "here is the representation"
- **304 Not Modified** means "use the copy you already have"

`304 Not Modified` is not a failure and it is not an empty `200`. It is a successful cache revalidation response.

## The Core Difference

**200 OK** is the baseline response. The server sends headers and a full body.

**304 Not Modified** only appears when the client asks a conditional caching question, such as "has this changed since the version with this ETag?" If the answer is no, the server skips the body and the client reuses its cached content.

## Why You See 304 In Real Life

You usually encounter `304` in one of these situations:

- refreshing a page where CSS or JS is already cached
- debugging stale assets and watching `ETag` or `Last-Modified`
- trying to understand why the browser made a network request but did not download the full file again

304 only happens when the client sends a conditional request:

```text
# First request — client has nothing cached
GET /styles.css HTTP/1.1

# Server responds with full content + cache validators
HTTP/1.1 200 OK
ETag: "abc123"
Cache-Control: max-age=3600
[full CSS body]

# Later — cache has expired, client revalidates
GET /styles.css HTTP/1.1
If-None-Match: "abc123"

# Resource unchanged — server sends 304, no body
HTTP/1.1 304 Not Modified
ETag: "abc123"
Cache-Control: max-age=3600
```

## Comparison Table

| | 200 OK | 304 Not Modified |
|---|---|---|
| Response body | Yes (full resource) | No |
| Triggered by | Any GET/HEAD | Conditional GET (If-None-Match / If-Modified-Since) |
| Client action | Store in cache | Use existing cached copy |
| Bandwidth used | Full resource size | Headers only (~200–500 bytes) |
| Requires prior request | No | Yes (needs cached ETag or Last-Modified) |

## ETag vs Last-Modified

Servers can use two mechanisms to enable conditional requests:

**ETag** — a content fingerprint, usually a hash of the response body:
```http
ETag: "d41d8cd98f00b204e9800998ecf8427e"
```
Client sends back as `If-None-Match: "d41d8cd98f00b204e9800998ecf8427e"`.

**Last-Modified** — a timestamp of when the resource last changed:
```http
Last-Modified: Tue, 18 Feb 2026 00:00:00 GMT
```
Client sends back as `If-Modified-Since: Tue, 18 Feb 2026 00:00:00 GMT`.

ETags are more reliable. A file can be regenerated with identical content (same ETag, different timestamp) or have its mtime updated without content changes. Use ETags when your server can compute them cheaply.

## Cache-Control Interaction

304 only comes into play after a cache entry expires. The flow is:

1. **Fresh cache** (`max-age` not exceeded) — browser uses cached copy directly, no request sent
2. **Stale cache** (`max-age` exceeded) — browser sends conditional request → server returns 304 or 200
3. **No cache validators** (no ETag, no Last-Modified) — browser must fetch full 200

`Cache-Control: no-cache` forces revalidation on every request (step 2 always), but still allows 304 if the server supports it. `Cache-Control: no-store` disables caching entirely — always 200.

## Common Mistakes

**Treating 304 like a missing response** — it is not missing anything. The response is telling the client that the cached body is still authoritative.

**Not sending ETag or Last-Modified** — without validators, the browser cannot revalidate efficiently and must fall back to full `200` responses.

**Confusing 304 with 204** — `304` means "reuse your cached copy." `204 No Content` means "the request succeeded, and there is no body to send for this operation."

**Expecting 304 for POST** — conditional cache revalidation is for `GET` and `HEAD`, not for write-oriented methods.

## FAQ

### Does a 304 response have a body?

No. A 304 response must not include a message body. The entire point is that the client already has the body in its cache. The server only sends headers — typically Cache-Control, ETag, and Vary — to update the cached entry metadata.

### What triggers a 304 response?

A conditional request triggers a 304. The client sends If-None-Match (with a cached ETag value) or If-Modified-Since (with a cached date). The server compares these against the current resource. If nothing has changed, it returns 304. If the resource has changed, it returns 200 with the new content.

### What is the difference between ETag and Last-Modified for cache validation?

ETag is a content fingerprint (usually a hash) — it changes whenever the content changes, regardless of time. Last-Modified is a timestamp. ETags are more reliable: a file can be modified and restored to its original content (same ETag, different timestamp) or have its timestamp updated without content changes (same content, different timestamp). Prefer ETags when possible.

### Can I get a 304 for a POST request?

No. A 304 is only returned for GET and HEAD requests that carry If-None-Match or If-Modified-Since. For POST, PUT, PATCH and DELETE, a failed precondition (If-Match, If-Unmodified-Since) produces 412 Precondition Failed instead (RFC 9110 section 13.1).

### Why does my browser sometimes get 200 even when the resource has not changed?

Several reasons: the server may not implement ETags or Last-Modified headers; the Cache-Control header may have no-cache (which forces revalidation but the server still returns 200 if it does not support conditional requests); or the cache entry may have expired and the browser is making a fresh request rather than a conditional one. Check that your server sends ETag or Last-Modified on the initial 200 response.

## References

- [MDN Web Docs: 304 Not Modified](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/304)
- [MDN Web Docs: HTTP Caching](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching)
- [RFC 9110: Conditional Requests](https://www.rfc-editor.org/rfc/rfc9110#section-13)
