The complete guide to HL7v2 to FHIR migration
Migrating from HL7v2 to FHIR is rarely a clean cutover. It is a translation problem you live with for a while, where the same patient fact has to line up across two very different representations. Here is how to plan it, map it, validate it, and avoid the failure modes that show up in production.
Why the dual-run period is the hard part
The clever bit of a migration is not converting one message. It is the stretch of time where a v2 feed and a FHIR feed both describe the same events and have to agree. During this dual-run period an admission arrives as an ADT^A01 message and, at the same moment, as a FHIR Encounter and Patient. If the MRN in PID maps to a different identifier system than the one your FHIR consumers expect, the two records will not reconcile and you get duplicate patients.
Plan the dual run deliberately. Decide which system is the source of truth for each data type, how you will detect drift between the v2 and FHIR views, and what happens when they disagree. The translation layer is the easy code to write. Keeping two representations consistent under real traffic is the work.
- Pin identifier systems first. Every MRN, IHI, or Medicare number needs a stable system URI before anything else maps cleanly.
- Pick a reconciliation key per resource type so you can match the v2-derived record to the FHIR record.
- Instrument both feeds so you can compare counts and spot a feed that silently stops.
A message-type-by-message-type mapping approach
Do not try to map HL7v2 as one giant transform. Work message type by message type, because each maps to a different FHIR resource with its own rules. Start with the demographics backbone, then layer on the clinical content.
Demographics and encounters (ADT)
ADT messages carry the patient and visit. ADT^A01 (admit), A04 (register), A08 (update), and A03 (discharge) all map PID demographics to a FHIR Patient and PV1 to an Encounter. See the field-by-field rules in the mapping library, and the worked example in mapping HL7v2 ADT to a US Core Patient.
Results and observations (ORU)
ORU^R01 carries lab and other results. Each OBX becomes a FHIR Observation: OBX-3 to Observation.code with a LOINC system, OBX-5 to value[x], OBX-6 to the unit, OBX-7 to the reference range. The tutorial on turning ORU^R01 results into FHIR Observations walks the whole path.
Orders, scheduling, immunizations, documents
ORM^O01 and RDE^O11 map to ServiceRequest and MedicationRequest, SIU^S12 to Appointment, VXU^V04 to Immunization, and MDM^T02 to DocumentReference. The HL7v2 message reference covers the segments for each, and the OBX segment is worth knowing cold since it recurs across result and document messages. Keep the HL7v2 segment reference open while you map.
Conformance: US Core, AU Core, IPS
Base FHIR is permissive. Real exchange happens against a profile. In the United States that is US Core; in Australia, AU Core; for cross-border patient summaries, the International Patient Summary. Conforming to a profile is what makes your data usable by someone else.
- US Core adds must-support elements and the US-specific race, ethnicity, and birth sex extensions on Patient.
- AU Core centres on Australian identifiers, above all the 16-digit Individual Healthcare Identifier (IHI) with its specific system namespace.
- The IPS defines a minimal, internationally interoperable summary, useful when the data crosses jurisdictions.
Map to the profile from the start, not as an afterthought. A Patient that is valid base FHIR but missing a US Core must-support extension will fail downstream the day a real consumer connects.
Validation strategy
Validate at two levels and at two times. Validate the inbound HL7v2 message for structure before you trust it, and validate the outbound FHIR resource against its profile before you publish it. Do the fast checks during development and the authoritative checks in your pipeline.
- Parse and structurally check each v2 message with the HL7v2 validator so a malformed feed fails fast.
- Validate the resulting resource against US Core or AU Core in the FHIR validator during iteration; it runs client-side so you can use real data safely.
- Use the FHIRPath evaluator (see the FHIRPath cheat sheet) to assert specific invariants, for example that every Patient has an identifier with a system.
- Reach for the authoritative HL7 FHIR Validator or HAPI in CI for the final, terminology-backed sign-off. See the FHIR validator comparison for when to use which.
Common failure modes
The same handful of problems account for most of the mapping bugs that reach production. Check these before anything more exotic. The validation error reference catalogues the specific messages you will see and how to resolve them.
- Identifier system left blank. An identifier value with no system URI is ambiguous and breaks reconciliation. This is the number-one cause of duplicate patients.
- Dates not reformatted. HL7v2 uses YYYYMMDD while FHIR expects ISO 8601. A birth date of 19800101 must become 1980-01-01.
- Timezone loss. v2 timestamps may carry an offset; dropping it shifts events across day boundaries.
- Coded values copied verbatim. A v2 sex or status code needs a value-set lookup, not a straight copy, or the binding fails.
- Missing must-support elements. The resource is valid base FHIR but fails the profile because a required US Core or AU Core element is absent.
Tooling
You need three things during a migration: a way to understand and validate the v2 input, a way to build and test the mapping, and a way to validate the FHIR output against a profile. Interoperall covers all three client-side, so you can work with real messages without uploading PHI.
For the production runtime itself you still want an interface engine to route and deliver live traffic. A toolkit complements that engine rather than replacing it; the integration engine comparison explains the split. Use the toolkit to design and prove the mapping, and the engine to run it.
Frequently asked questions
Do I have to replace HL7v2 to adopt FHIR?
No. Most organisations run HL7v2 and FHIR side by side for years. New consumers read FHIR while legacy feeds keep flowing in v2. Migration usually means adding a v2-to-FHIR translation layer rather than ripping out working interfaces.
What is the hardest part of an HL7v2 to FHIR migration?
The dual-run period, where the same clinical fact exists in both a v2 feed and a FHIR resource and the two must agree. Reconciling identifiers, timing, and coded values across the two representations is harder than any single field mapping.
How do I know my FHIR output is correct?
Validate against the profile you target, such as US Core or AU Core, not just base FHIR. Profile validation catches missing must-support elements, bad bindings, and identifier system problems that base validation passes.
Where do most v2-to-FHIR mappings go wrong?
Identifier systems left blank, dates not reformatted to ISO 8601, coded values copied across without a value-set lookup, and timezone loss in timestamps. These are the recurring failure modes worth checking first.