The short answer
When HAPI FHIR answers 200 to an update but meta.versionId stays the same, the server decided your update didn’t change the resource. HAPI FHIR JPA doesn’t create a new version for an update that changes nothing; the setting is suppressUpdatesWithNoChange, and it’s on by default.1 In our October 2026 study, all four server setups we tested, two HAPI configurations, the HAPI public server and the Firely sandbox, answered 200 to an update that changed nothing and created no new version.4 That’s normal. It becomes a problem only when you meant to change something, so compare the stored resource with what you intended.
Deedglass
The server said “saved”. Deedglass looked.
Deedglass reads it back
Why HAPI keeps the version
In FHIR R4, Meta.versionId changes each time the content of the resource changes, and the server usually manages it.2 A version records a change. If nothing changed, there’s nothing to record, and a new version would only add noise to the history that vread and _history expose.3
HAPI implements this with suppressUpdatesWithNoChange. With it on, which is the default, an update whose content matches the current version is answered with success and leaves the version where it was.1 You can turn it off if you want every update to create a version, but most deployments leave it on.
Code A request one program sends to another over the web.
GET /fhir/Patient/p-9 → 200, meta.versionId "4"
PUT /fhir/Patient/p-9 (same content)
→ 200, meta.versionId "4" (no change, no new version)
PUT /fhir/Patient/p-9 (new phone number)
→ 200, meta.versionId "5"
What we measured
On 26 October 2026 we sent synthetic FHIR R4 writes to HAPI FHIR JPA Server 8.12.0 run locally twice (starter defaults; request validation on), the HAPI public test server (build 8.13.10-SNAPSHOT) and the Firely Server 6.11.0 public sandbox, and read every one back.4
| Setup | Update with no change | New version? |
|---|---|---|
| HAPI 8.12.0, defaults | 200 | No |
| HAPI 8.12.0, request validation on | 200 | No |
| HAPI public test server | 200 | No |
| Firely Server 6.11.0 sandbox | 200 | No |
All four behaved the same.4 So this isn’t a HAPI quirk to work around; it’s how R4 servers commonly behave, and the reply can’t show whether anything was written.
When no new version hides a problem
“No new version” is correct when you sent the same content. It hides a problem when you sent different content and the server ended up storing the same thing as before. Our study found two ways that happens with invalid data.
Across the four setups, 29 of 72 invalid writes got a success reply, and 8 of those were stored with part of what was sent removed.4 One example: a status sent as ["final"], an array where R4 expects a single code, was answered 201 by HAPI’s defaults and stored with no status at all; HAPI with request validation on refused it with 422.4 Validation strictness is a server setting R4 leaves to the server, and HAPI documents its lenient parser as the default.4
Put those together. If an agent’s update changes only a field the server’s parser drops, the stored content may match the previous version. The server then sees no change, keeps the version, and answers 200. The agent sees success. The record is unchanged. Nothing in the reply distinguishes “you sent the same thing” from “your change was dropped on the way in”.
| What the agent sent | What was stored | Reply | Version |
|---|---|---|---|
| Same content as stored | Same content | 200 | Unchanged |
| A real change, valid | The change | 200 | +1 |
| A change in a field the parser drops | Previous content | 200 | Unchanged |
The third row is the one to catch. It looks identical to the first in the reply and in the version history. Only a comparison between what the agent meant to store and what the server now holds tells them apart, which is why the check has to live outside the server’s reply.
How to check an update
- Record the version before and after. Read
meta.versionIdbefore the update and again after. - If the version moved, read the new version and compare the fields you meant to change with what’s stored.
- If the version didn’t move, compare the content you sent with what’s stored. If they differ, your change didn’t land. Don’t log success.
- Turn on request validation in environments where agents write, so malformed data is refused rather than trimmed. Our request validation post covers where HAPI validates and what slips past.
- Use
If-Matchwith the version you read, so a concurrent change gets refused rather than overwritten; 409 or 412 both mean “refused”.
This is the read-back pattern: the write isn’t done when the server replies, it’s done when a read shows the state you intended.
A checklist
- Expect 200 with no new version for no-op updates on HAPI and Firely.4
- Keep
suppressUpdatesWithNoChangeon unless you have a reason; it keeps history meaningful.1 - Compare intended fields with stored fields after every agent update, whether or not the version moved.
- Validate agent writes at the server, and log refusals as refusals.
- Store the before and after version ids with every agent action, so a reviewer can
vreadboth.3 - Name your server version and configuration when you document behaviour; it varies by build.
Questions people ask
Is it a bug that HAPI didn’t create a new version?
No. HAPI suppresses versions for updates that change nothing by default1, and all four setups we tested did the same.4
How do I force a new version for every update?
In HAPI, turn off suppressUpdatesWithNoChange in the JPA storage settings.1 Think about whether you want that: empty versions make history harder to read.
Does a 200 with the same version mean my change was saved?
No. It means the server found nothing different between what it stored and what was there. Compare your intended change with the stored resource.
The full case list is in our FHIR write integrity study. For how hospitals can run these checks before any agent touches a live system, see the hospital page, and for writing the before-and-after versions into a record anyone can check, the provenance spec. 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.