Category: Concurrency Control
Description
PUT requests should support conditional request headers (If-Unmodified-Since or If-Match) to prevent lost updates in concurrent scenarios.
What It Checks
This rule validates that PUT operations include at least one of the following conditional request headers:
If-Unmodified-Since: Allows the request only if the resource hasn't been modified since the specified dateIf-Match: Allows the request only if the resource's ETag matches the provided value
The rule checks:
- All PUT operations that include a request body
- Verifies that at least one conditional header parameter is defined
- Case-insensitive header name matching (both
If-Matchandif-matchare accepted)
Why It Matters
Conditional headers are critical for preventing the "lost update problem" in distributed systems:
- Concurrency safety: Prevents two clients from overwriting each other's changes without knowing it
- Data integrity: Ensures updates are only applied when the client has the current version of the resource
- Optimistic locking: Enables efficient concurrency control without database locks
- HTTP standard compliance: Follows RFC 7232 specifications for conditional requests
The Lost Update Problem
Without conditional headers, consider this scenario:
- Client A reads resource (version 1)
- Client B reads resource (version 1)
- Client A updates resource (version 2)
- Client B updates resource based on old version 1 → Client A's changes are lost!
Conditional headers prevent this by failing Client B's request with 412 Precondition Failed.
Examples
Good ✅
paths:
/users/{userId}:
put:
summary: Update a user
parameters:
- name: userId
in: path
required: true
schema:
type: string
- name: If-Match
in: header
required: false
description: ETag from previous GET request
schema:
type: string
- name: If-Unmodified-Since
in: header
required: false
description: RFC 7231 date-time from Last-Modified header
schema:
type: string
format: date-time
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/User'
responses:
'200':
description: User updated successfully
'412':
description: Precondition failed - resource was modified
Using only If-Match (also valid) ✅
paths:
/articles/{articleId}:
put:
summary: Update an article
parameters:
- name: articleId
in: path
required: true
schema:
type: string
- name: If-Match
in: header
required: true # Can be required or optional
description: ETag value (e.g., "33a64df551425fcc55e4d42a148795d9f25f89d4")
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/Article'
responses:
'200':
description: Article updated
headers:
ETag:
description: New ETag of the updated resource
schema:
type: string
'412':
description: Precondition failed
Bad ❌
paths:
/users/{userId}:
put:
summary: Update a user
parameters:
- name: userId
in: path
required: true
schema:
type: string
# Missing If-Match or If-Unmodified-Since!
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/User'
responses:
'200':
description: User updated
Another Bad Example ❌
paths:
/products/{productId}:
put:
summary: Update product
parameters:
- name: productId
in: path
required: true
schema:
type: string
- name: X-Custom-Version # Custom headers don't count
in: header
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/Product'
responses:
'200':
description: Product updated
Configuration
This rule has no configuration options. It is either enabled or disabled.
When enabled, the rule requires at least one of the standard HTTP conditional headers (If-Unmodified-Since or If-Match) to be present on PUT operations.
How to Fix
Step 1: Choose Your Conditional Header Strategy
Option A: Use If-Match with ETags (Recommended)
- More robust and reliable
- Works with any type of change (content or metadata)
- Industry best practice
Option B: Use If-Unmodified-Since with Last-Modified
- Simpler to implement
- Only detects content changes
- Limited to 1-second precision
Option C: Support both (Best)
- Provides maximum flexibility for clients
- Allows clients to choose their preferred method
Step 2: Add the Header Parameter
Add the conditional header to your PUT operation's parameters:
parameters:
- name: If-Match
in: header
required: false # Can be true if you want to enforce it
description: |
ETag value from a previous GET request. Request fails with 412
if the resource has been modified since.
schema:
type: string
example: '"33a64df551425fcc55e4d42a148795d9f25f89d4"'
Or for If-Unmodified-Since:
parameters:
- name: If-Unmodified-Since
in: header
required: false
description: |
RFC 7231 date-time. Request succeeds only if the resource
has not been modified since this date.
schema:
type: string
format: date-time
example: "Wed, 21 Oct 2015 07:28:00 GMT"
Step 3: Document the 412 Response
Add a 412 Precondition Failed response to indicate when the conditional check fails:
responses:
'200':
description: Resource updated successfully
headers:
ETag: # Return new ETag for next update
schema:
type: string
'412':
description: |
Precondition Failed - The resource was modified by another client.
Fetch the latest version and retry your update.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
Step 4: Update GET to Return Matching Headers
Ensure your GET operations return the corresponding headers:
For If-Match, return ETag:
/users/{userId}:
get:
responses:
'200':
description: User details
headers:
ETag:
description: Version identifier
schema:
type: string
For If-Unmodified-Since, return Last-Modified:
/users/{userId}:
get:
responses:
'200':
description: User details
headers:
Last-Modified:
description: Last modification time
schema:
type: string
format: date-time
Step 5: Implement Server-Side Logic
Your API implementation must:
- Store and return ETags or Last-Modified timestamps
- Check the conditional header on PUT requests
- Return
412 Precondition Failedif the condition fails - Only apply the update if the condition passes
Related Rules
- Rule 58: Last-Modified - Ensures GET responses include Last-Modified header for cache control
- Rule 53: Create/Update Return - Ensures PUT operations return the updated resource
- Rule 34: Code 201 Create - Related to proper status codes for resource creation