Two Bundles containing the same resources in different order produce an unusable diff. Every entry looks changed because every index shifted. The right pre-diff step is to align entries between the two Bundles so the comparator can diff matching pairs. The site's Resource-vs-resource comparator does this by default when you paste two Bundles. For the wider FHIR framing, our FHIR coverage has more.
Why Alignment Matters
- Bundle A:
[Patient, Observation, Condition] - Bundle B:
[Observation, Patient, Condition]
Same resources, different order. A positional diff says every entry changed. An aligned diff correctly identifies zero changes.
For the resource-level noise-reduction pattern, diffing two FHIR resources without drowning in reference noise is the entry.
Alignment Keys
Options for aligning entries between Bundles:
entry.fullUrl— best for transaction-like Bundlesentry.resource.id— good when ids are stable across Bundlesentry.resource.resourceType + identifier— for cross-server comparability- Content hash — as a last resort
The right key depends on the Bundles. Transactions use fullUrl. Search results use id. Cross-server comparisons use identifier.
When Order Is Load-Bearing
Some Bundles have semantic order:
- Transaction Bundles — server processes entries in order
- Message Bundles — event order matters
- Composition Bundles — narrative section order
For these, do not align by key. Diff by position and treat any order change as a real difference.
For the split, structural diff vs semantic diff for FHIR JSON is the entry.
Handling Unmatched Entries
- Entry in A but not B — "removed"
- Entry in B but not A — "added"
- Entries matched by key — recursive diff
Each category surfaces separately in the output. The comparator groups them for readability.
The Duplicate-Key Case
Two Bundles may have entries with the same key. A single-pass alignment picks one arbitrarily and produces confusing results.
The right pattern:
- Group entries by key on both sides
- Pair them up in order within each key group
- Extra entries in either group become "added" or "removed"
The comparator implements this multi-map pairing by default.
The Very-Large-Bundle Case
Aligning by key requires holding both Bundles in memory. Large Bundles (bulk export shape) may not fit.
Options:
- Stream, sort both sides externally by key, then merge-diff
- Chunk by resource type and diff per type
- Sample and diff representative subsets
For most day-to-day diffing, the in-memory approach is fine. For very large Bundles, the diff tool needs to be a specialized pipeline.
Reference Rewriting During Alignment
Entries in one Bundle may reference each other by urn:uuid: while the other uses server-assigned ids. Aligning without rewriting produces reference-diff noise on every entry.
The right pre-diff: build a URN-to-id map from the alignment step and rewrite references before the recursive diff.
Output Structure
A Bundle diff produces:
- Per-entry changes with alignment keys
- Added/removed entries with resource type and key
- Summary counts
Reviewers can drill in from the summary. Automated tools consume the machine-readable per-entry changes.
For PR review specifically, reviewing FHIR diffs in a pull request is the entry.
Testing The Alignment
Every alignment implementation should be tested on:
- Same Bundle, same order → empty diff
- Same content, shuffled order → empty diff (when order is not load-bearing)
- One entry different → single-entry diff
- Duplicate keys with different content → per-instance diff
If any of these produce unexpected output, the alignment is wrong.
The Short Version
Align by fullUrl or id before diffing Bundles. Handle unmatched entries as added/removed. Rewrite cross-entry references pre-diff. Preserve order when it is load-bearing. Test the alignment logic explicitly.

Sources
- HL7 canonical FHIR Bundle resource specification covering - HL7 canonical FHIR Bundle resource specification covering entry semantics
