FHIR referential integrity: when a server stores a reference to nothing

An Observation points at Patient/123, and Patient/123 doesn't exist. Will the server refuse it? It depends on the server and its settings. Here is what R4 says, what we measured, and what to check.

The short answer

Whether a FHIR server refuses a write that references a resource that doesn’t exist depends on the server and how it’s configured. R4 says a literal reference “SHALL be resolvable”, but its RESTful API page doesn’t say what a create or update must do when it isn’t.1 In our October 2026 study, the Firely Server 6.11.0 sandbox accepted a reference to a Patient that doesn’t exist, and a transaction holding one, storing 3 of 3 entries; HAPI FHIR refused both with 400 and stored nothing from the transaction.4 If agents write to your server, resolve every reference before you send, and test what your server does.

A FHIR server can accept a write and quietly drop a field. Deedglass reads the resource back and names what differs. Made-up resource.

What R4 says

R4 separates two kinds of reference:1

Kind Example Must the target exist?
Literal "reference": "Patient/123" It SHALL be resolvable to an actual resource
Logical "identifier": {"system": "...", "value": "00042"} No requirement; servers may accept, resolve or reject

The literal rule describes what a correct reference is. It doesn’t spell out whether the server must check it on every write. The RESTful API page talks about referential integrity for deletes and for conditional references inside transactions, but not for a plain create or update that points nowhere.1 So enforcement is a design choice each server makes, and often a setting.

Inside a transaction, references between entries are resolved by the server, and a conditional reference that matches zero or several resources fails the whole transaction.3 A reference to a plain id that doesn’t exist anywhere is a different case, and that’s the one servers treat differently.

What we measured

On 26 October 2026 we sent synthetic writes to four setups: HAPI FHIR JPA Server 8.12.0 run locally twice (starter defaults; request validation on), the HAPI public test server and the Firely Server 6.11.0 public sandbox, and read every one back.4

Write Firely 6.11.0 sandbox HAPI
Observation referencing a Patient that doesn’t exist Stored Refused, 400
Transaction holding such a reference Stored 3 of 3 Refused, 400; nothing stored

Source:4. Referential integrity is a server setting, so this describes those setups as we ran them, not every deployment of either product. The full case list is on the FHIR write integrity study.

How servers differ by design

Different products make this a visible choice. Google Cloud’s Healthcare API, for example, enforces referential integrity within a FHIR store unless it has been switched off: a create, update or patch fails if the result references a resource or version that doesn’t exist, and a delete fails if other resources point at it. It can be switched off with disableReferentialIntegrity when the store is created, and its bulk import method doesn’t enforce it, so references become consistent once everything has loaded.2

That last point shows why some deployments turn checks off: bulk loads arrive out of order, and a strict server would refuse an Observation that arrives a second before its Patient. The trade-off is that a lenient store will also accept a reference that never becomes valid.

Why a dangling reference is a problem

A reference to nothing isn’t harmless data. In practice:

  • The record is orphaned. An Observation whose subject doesn’t exist won’t show up in that patient’s chart, because there is no such patient to query.
  • It may be the wrong patient later. If an id is reused or created afterwards by another process, the reference starts pointing at a record nobody intended.
  • Agents produce them easily. A model that writes Patient/123 from memory, or from a search result in a different system, will produce a syntactically perfect reference to a resource that isn’t there.
  • The reply looks like success. On a lenient server the write gets 201, the agent reports done, and nothing flags the gap.

None of this is about clinical judgement. It’s about whether the record links to what it claims to link to.

Checking references before and after a write

  1. Resolve before writing. For every literal reference in the body, GET the target. If any returns 404 or 410, don’t send the write.
  2. Prefer references the agent just read. Take ids from resources fetched in the same step, not from model memory or another system’s ids.
  3. Use logical references deliberately. If you only have a business identifier, either resolve it to a literal id first or send it as an identifier reference, knowing servers may accept, resolve or reject it.1
  4. In transactions, use urn:uuid full URLs for resources created in the same Bundle, and let the server replace them.3
  5. Read back after the write and follow each reference in the stored resource. Every one should resolve.
  6. Know your server’s setting. Test once with a deliberate dangling reference against a sandbox and write down the version and configuration.
Check When Catches
GET each referenced id Before the write References to nothing
Ids only from fresh reads While building Ids from memory or another system
Follow references on read-back After the write Anything a lenient server stored anyway
Sandbox test Once per server version What your server actually enforces

Questions people ask

Does FHIR require servers to enforce referential integrity?

R4 says literal references shall be resolvable, but it doesn’t spell out that a server must refuse a create or update that points to a missing resource.1 Servers decide, often through a setting.

Why did HAPI refuse and Firely accept?

They’re configured differently in the setups we ran. HAPI refused both writes with 400; the Firely sandbox stored them.4 Either product may behave differently with other settings.

Is a logical reference safer?

It’s more honest about what you know, but R4 places no requirement that the target exists, and servers may accept it untouched.1 Resolve it when you can.

To watch a read-back catch a write that said success, run npx deedglass health pilot, a 15-minute pilot on synthetic data.5 The healthcare overview and provenance spec show how checks like these get recorded. To try approvals and read-backs on a real database in your browser, open the playground; to grade what an agent can reach on your own machine, run npx deedglass scan (the grade, explained).

Written with AI assistance and checked against our claims register.

The takeaway

Firely's sandbox stored writes pointing at a missing Patient and HAPI refused them; R4 leaves this to the server, so resolve references first.

Read nextFHIR conditional create: when If-None-Exist stores nothing, and when it stores two Try it: install in 7 s

Related on this site

More posts

Sources

  1. 1HL7 FHIR R4: Resource References · hl7.org · checked 27 October 2026
  2. 2Google Cloud Healthcare API: FHIR referential integrity · docs.cloud.google.com · checked 27 October 2026
  3. 3HL7 FHIR R4: RESTful API, batch and transaction · hl7.org · checked 27 October 2026
  4. 4Deedglass research: FHIR write integrity 2026 · deedglass.com · checked 27 October 2026
  5. 5Deedglass for hospitals: the 15-minute pilot · deedglass.com · checked 27 October 2026

Published 27 October 2026, sources last checked 27 October 2026. If something here is out of date, write to hello@deedglass.com.

Try it on your own setup

npx deedglass scan

Grades only your own setup. Nothing is uploaded.

Pricing