428 Precondition Required
The server requires the request to be conditional to prevent the 'lost update' problem.
Meaning & Description
Common Causes
- The client failed to include an `If-Match` header with the ETag of the resource being updated.
- The client failed to include an `If-Unmodified-Since` header with the last known modification date.
- The API documentation wasn't followed regarding concurrency control requirements.
How to fix a 428 error
- Perform a GET request to the resource to obtain its current `ETag` or `Last-Modified` header.
- Include the retrieved ETag in the `If-Match` header of your PUT/PATCH/DELETE request.
- If the server responds with a 412 Precondition Failed after adding the header, fetch the resource again and merge any upstream changes before retrying.
Browser & SEO Behaviour
Browser Behavior
Browsers rarely encounter 428 errors during typical web navigation, as it's primarily used for API interactions. If encountered, the raw response is usually displayed.
SEO Impact
None. Crawlers only use GET requests and do not attempt state-changing operations that require preconditions.
CDN Behavior
CDNs pass 428 responses through to the client without caching, as they indicate a state-modification failure.
Code Examples
PUT /api/articles/123 HTTP/1.1
Host: api.example.com
Content-Type: application/json
{"title": "Updated Title"}app.put('/api/inventory/:id', (req, res) => {
if (!req.headers['if-match']) {
return res.status(428).json({
error: "Precondition Required",
message: "You must provide an If-Match header with the latest ETag to update this resource."
});
}
// Proceed with validation
});@app.put("/api/users/{user_id}")
def update_user(user_id: int, if_match: str = Header(None)):
if not if_match:
raise HTTPException(status_code=428, detail="If-Match header is missing")
# Check if ETag matches current DB state...
return {"status": "success"}func updateArticle(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("If-Match") == "" {
http.Error(w, "Precondition Required: If-Match header missing", 428)
return
}
// Handle update logic
}[HttpPut("{id}")]
public IActionResult Update(int id, [FromHeader(Name = "If-Match")] string eTag) {
if (string.IsNullOrEmpty(eTag)) {
return StatusCode(428, "An If-Match header is required for updates.");
}
// Check ETag and update DB
return Ok();
}curl -X PUT https://api.example.com/items/1 \
-H "If-Match: \"33a64df551425fcc55e4d42a148795d9f25f89d4\"" \
-H "Content-Type: application/json" \
-d '{"stock": 4}' \
-iRaw HTTP Response Example
HTTP/1.1 428 Precondition Required
Content-Type: application/json
Content-Length: 104
{
"error": "Precondition Required",
"message": "Please include an If-Match header with the ETag."
}Real-world Examples
Frequently Asked Questions
What is the 'lost update' problem?
It occurs when Client A and Client B both read a resource. Client A updates it, and then Client B updates it, accidentally overwriting Client A's changes because Client B was unaware of them.
What is the difference between 428 Precondition Required and 412 Precondition Failed?
428 means you didn't provide a precondition header (like `If-Match`) at all. 412 means you *did* provide one, but it didn't match the current state on the server (e.g., your ETag was outdated).
Which HTTP headers are considered preconditions?
The primary precondition headers are `If-Match`, `If-None-Match`, `If-Modified-Since`, `If-Unmodified-Since`, and `If-Range`.
Do I always need to return 428 for PUT requests?
No. You only return 428 if your specific API endpoint strictly enforces optimistic concurrency control. For many simple APIs, last-write-wins is acceptable.
How do I fix a 428 error?
Fetch the resource again using a GET request, capture the `ETag` from the response headers, and include that value in an `If-Match` header in your subsequent PUT/PATCH request.
Did You Know?
RFC 6585 introduced the 428 status code in 2012, along with 429 Too Many Requests and 431 Request Header Fields Too Large.
Implementing optimistic concurrency control via ETags and 428/412 is universally preferred in REST over pessimistic locking (423 Locked).
ETags (Entity Tags) are essentially hashes or version numbers of the resource state.
Developer Tips
- Always provide a helpful error message with a 428 response instructing the developer to fetch the latest state and use an `If-Match` header.
- If your database uses a `version` or `updated_at` column, use it to generate your ETags for seamless concurrency control.
- Don't enforce 428 on idempotent DELETE requests unless business logic explicitly requires ensuring the client knows exactly what they are deleting.
Interview Questions
These are questions you might face in a backend or API design interview that touch on HTTP 428.
What HTTP status code is used to prevent the 'lost update' problem, and how?
The 428 Precondition Required status code prevents the lost update problem by forcing clients to include conditional headers (like If-Match). This ensures they are updating the latest known state.
Explain the difference between optimistic and pessimistic locking in HTTP.
Optimistic locking assumes conflicts are rare; it uses ETags and precondition headers (428, 412) to fail updates only when a conflict actually occurs. Pessimistic locking (423 Locked) explicitly locks the resource so nobody else can touch it until the lock is released.
If a client sends an `If-Match` header but the ETag doesn't match the server's current version, what status code should be returned?
The server should return 412 Precondition Failed. 428 is only used when the precondition header is completely missing.
Common Interview Mistakes
- Confusing 428 Precondition Required with 412 Precondition Failed.
- Failing to explain the 'lost update' problem or optimistic concurrency control.
- Assuming 428 relates to missing API keys or authorization headers.