FHIR servers expose three meaningful ways to write a changed resource, and integration teams that conflate them tend to discover the difference in a postmortem. PUT replaces the resource entirely, PATCH applies a partial change, and upsert combines a write and a conditional create into one operation. The trade-off is real and shows up most clearly when HL7v2 feeds carry only a subset of a Patient or Encounter and the engine has to decide what to do with the rest of the record. For broader background, see more on FHIR product trade-offs.
The general selection question for the FHIR server itself sits in the FHIR server buyer's guide; this piece focuses on the write-semantics axis that decides whether downstream data stays clean.
PUT: Full Replacement Semantics
PUT replaces the resource at a given URL with the body provided. Any field absent from the payload is gone after the write. For HL7v2-to-FHIR pipelines, this is the most dangerous default. An ADT update that carries only the demographic block will overwrite the contact details, the language preference, and the registered communication channels with nothing if the engine treats the ADT as a full Patient body.
PUT is the right choice when the upstream system genuinely owns the entire resource and authoritatively republishes it on each change. That pattern shows up in payer-to-provider Member.write flows and in registry-of-record systems. It is the wrong choice for piecemeal HL7v2 feeds where each segment carries part of the picture.
PATCH: Partial Change Semantics
PATCH applies a defined set of operations (add, replace, remove, test) against the existing resource. The unchanged fields stay where they were. For HL7v2 feeds that update only a subset of a Patient or Encounter, PATCH is the natural fit: the engine writes only what changed and leaves the rest alone.
The trade-off is two-fold. First, PATCH support is uneven across FHIR servers; some support FHIRPath Patch, some support JSON Patch, and a few support neither cleanly. Second, generating the patch document from an HL7v2 segment requires a diff step that the engine has to do explicitly. Tools like Interbox bundle hash-based diffing that chooses PUT vs PATCH vs skip on each write into the standard worker chain, which collapses the diff step into the write path; engines that do not ship with it have to add the comparison logic themselves.
Upsert: Conditional Create or Update
The upsert pattern uses a conditional update (PUT against a search URL like Patient?identifier=...) to either create the resource if it does not exist or update it if it does. The semantics are convenient for the ingestion-from-scratch case and for replays where the engine does not know whether the resource is already in the store.
In practice the upsert path inherits PUT's full-replacement risk unless the engine first reads the existing resource, merges, and posts the merged body. That extra read-merge-write step is where most upsert implementations diverge from each other; the top 5 HL7 v2 to FHIR conversion engines for US hospitals walkthrough covers how some engines handle this.
How Teams Actually Pick
The honest pattern: PATCH for HL7v2-driven updates where the message owns only a slice of the resource, PUT for upstream systems that own the whole record, upsert for ingestion or replay where existence is uncertain. Mature pipelines run all three depending on the source. The trade-off is not which is best in the abstract but which matches the semantics of the upstream feed.
Sources
- build.fhir.org R6 ballot - HTTP PUT/PATCH semantics and conditional update




