A nurse and a doctor open the same Patient record at nine minutes past. They edit different fields and both click save. Without conditional update, whoever clicks last wins and the other set of edits vanishes silently. That is a lost write, and it is exactly the failure mode FHIR REST is designed to prevent.
The site's FHIR RESTful API cheatsheet has the header pattern. For the broader REST framing, the rest of the FHIR series has related pieces.
The Header That Does the Work
On read, the server returns an ETag. On update, the client sends the ETag back as an If-Match header. If the resource has not changed since the read, the update succeeds. If it has changed, the server responds with 412 Precondition Failed and the write is rejected.
This is optimistic concurrency, and it is the FHIR way. Pessimistic locking is not part of the spec, and it does not scale to a REST surface anyway. The bet is that most writes do not collide, and the ones that do can be handled at the client.
What Happens on 412
A well-designed client handles 412 with a three-step recovery:
- Re-read the current resource and its new ETag
- Present the conflict to the caller with both versions if possible
- Retry the update with the fresh ETag after the caller reconciles
Automated retry without user reconciliation is dangerous — it can commit a resource that the user did not review. Automated retry with automatic merge is only correct when the merge rules are declared and tested per resource type.
Why Optimistic Beats Pessimistic in Practice
Pessimistic locking requires a lock service, a stale-lock cleanup story, and a UX that surfaces "someone else is editing this" to every reader. Every one of those is expensive, and stale locks are the usual failure mode in production. Optimistic concurrency requires none of it. The 412 handles the collision, and the collision rate is usually low enough that the extra round trip is a good trade.
Combining With Conditional Create
If-None-Exist is the sibling for create: it prevents duplicate resource creation when two clients race to insert the same logical entity. The pattern is a search query in the header, and the server refuses to create if the query matches at least one resource. For the sibling read-side pattern, conditional read: If-None-Match and the caching wins it enables covers the read half.
The Trap: Missing ETag on the First Read
Every If-Match write depends on a valid ETag from a prior read. Clients that construct writes from cached form data without a prior read cannot use If-Match, and they lose the concurrency guarantee for that write. Two options:
- Perform a fresh read immediately before the write, use the returned ETag
- Store the ETag with the local form data at the moment of the initial load
The second is the pattern that survives real product flows. The first works but doubles the round-trip cost per write.
When to Use Weak vs Strong ETags
FHIR uses strong ETags by convention because version identifiers are exact. Weak ETags are for cases where semantic equivalence is enough, and FHIR resources are not usually those cases. Do not go looking for a weak-ETag pattern in FHIR — it is not there.
The Broader Verb Picture
Conditional update is one of a handful of interactions where FHIR REST behaves differently from a naive REST design. For the survey, the FHIR REST verbs that actually differ from vanilla REST is the entry. For the client-shape implications, designing a FHIR client that survives the tricky verbs is the follow-up.
Sources
- HL7 canonical section on optimistic concurrency + If-Match - HL7 canonical section on optimistic concurrency + If-Match