HL7 v2 to FHIR Migration: A Phased Playbook for European Hospitals and Integrators
How to run an HL7 v2 migration to FHIR without breaking clinical workflows: interface inventory, coexistence through an interface engine, a FHIR façade, the HL7 V2-to-FHIR mapping guide, historical data loads, validation and the retirement of v2 feeds.
Ala Ben Aicha

Direct answer
An HL7 v2 migration to FHIR is a staged coexistence, not a rip-and-replace. Keep the event-driven v2 feeds (ADT, ORM, ORU) running, translate them in an interface engine into profiled FHIR resources, expose those through a FHIR façade for new consumers, load historical records with idempotent conditional writes, and retire each v2 feed only after its consumers have moved and reconciliation has passed. Use the HL7 V2-to-FHIR implementation guide as the mapping baseline instead of inventing field maps from scratch.
Phased HL7 v2 to FHIR migration plan
| Phase | Goal | Main work | Exit criteria |
|---|---|---|---|
| 0. Inventory | Know what you are migrating | List every interface: sender, receiver, owner, v2 version, trigger events, volumes, identifier domains, Z-segments, downstream clinical use | Signed-off interface register and de-identified sample set per feed |
| 1. Coexistence | Translate without touching sources | Interface engine listens to existing v2 feeds and writes FHIR in shadow mode; nothing reads it in production yet | Daily reconciliation within agreed thresholds; exception queue owned |
| 2. FHIR façade | Give new consumers a FHIR API | Profiled read/search APIs (Patient, Encounter, Observation, DiagnosticReport, ServiceRequest) with SMART or backend authorization | First consumer live on FHIR; v2 still authoritative |
| 3. Historical load | Make history available on FHIR | Bulk load of past records with identifiers, Provenance and conditional writes | Counts and spot checks match the source; reload is idempotent |
| 4. Controlled writes | Accept FHIR input where needed | One authoritative write path, FHIR to v2 back-translation for legacy systems, loop prevention | Rollback tested; no duplicate orders in replay tests |
| 5. Retire feeds | Remove v2 interfaces one by one | Move each consumer to FHIR or a native interface, then switch off the feed | Zero consumers on the feed; archive and monitoring updated |
Phases 2 and 3 often overlap. The order that matters is that reconciliation, ownership and rollback exist before anything depends on the FHIR side.
Before cutover: evidence and rollback
Start with an interface inventory: sender and receiver owners, message versions, trigger events, daily volume, peak volume, identifier domains and downstream clinical dependencies. Agree mapping rules using representative, de-identified examples rather than a single happy-path message. The HL7 message types and sample messages guide is a useful checklist of the events you are likely to find.
For a read-only shadow phase, reconcile source events against target resources and investigate missing records, duplicates, late corrections and rejected terminology. Validate against the chosen FHIR profiles and use the FHIR R4 report model when separating report context from individual results.
Before enabling writes, define one authoritative write path, loop prevention and a replay policy. The cutover checklist needs named owners, acceptance thresholds, a stop condition and a tested rollback: disable the new writer, restore routing, reconcile the backlog and replay safely without duplicating orders.
The radiology and laboratory workflow guide gives concrete order/result scenarios to include in that test set. The OBX to Observation terminology mapping is the LOINC/SNOMED contract inside that engine. If the destination is an Australian FHIR server, validate against AU Core R2 rather than unconstrained R4 Patient and ServiceRequest profiles.
Why HL7 v2 stays during the migration
HL7 v2 is event-driven. When a patient is admitted, the hospital system sends an ADT^A01. An order goes out as an ORM^O01 (newer v2 versions also define OML order messages), and results come back as ORU^R01. These messages usually travel over MLLP on persistent TCP connections between the HIS, RIS, LIS and other departmental systems.
| Message Type | Trigger Event | Clinical Purpose |
|---|---|---|
| ADT (A01, A02, A03, A04...) | Admit, Transfer, Discharge | Synchronize patient demographics and visit data across all systems |
| ORM (O01) | Order Entry | Initiate clinical workflows — lab tests, imaging, procedures |
| ORU (R01) | Observation Result | Return diagnostic findings — lab values, radiology reports |
| SIU (S12, S14, S15...) | Scheduling | Appointment creation, modification, cancellation |
| DFT (P03) | Financial Transaction | Capture charges from clinical activity for billing |
| MDM (T01, T02) | Medical Document | Notify systems of new or updated clinical documents |
Interrupting these flows can delay care, break billing and create safety risks. That is why switching off v2 and expecting everything to work on FHIR the next morning fails. The transition has to be gradual and run in parallel.
The coexistence architecture
An interface engine sits in the middle, often Mirth Connect (NextGen Connect), acting as a bidirectional translator between the HL7 v2 world and the FHIR world. Other engines fit the same pattern; the interface engine comparison covers the trade-offs.
Legacy Systems Integration Engine Modern Applications
┌──────────────┐ ┌─────────────────┐ ┌──────────────┐
│ EHR │──ADT/ORM/ORU──►│ │──FHIR API──►│ Patient App │
│ (HL7 v2) │◄──ACK──────────│ Mirth Connect │◄─────────────│ (React/RN) │
└──────────────┘ │ │ └──────────────┘
┌──────────────┐ │ ┌───────────┐ │ ┌──────────────┐
│ RIS │──ORM/ORU──────►│ │ Transform │ │──FHIR──────►│ Analytics │
│ (HL7 v2) │◄──ACK──────────│ │ & Route │ │ │ Platform │
└──────────────┘ │ └───────────┘ │ └──────────────┘
┌──────────────┐ │ ┌───────────┐ │ ┌──────────────┐
│ LIS │──ORU──────────►│ │ HL7 ↔ FHIR│ │──Webhook───►│ Clinical │
│ (HL7 v2) │◄──ACK──────────│ │ Mapping │ │ │ Dashboard │
└──────────────┘ │ └───────────┘ │ └──────────────┘
└─────────────────┘
│
┌────▼────┐
│ FHIR │
│ Server │
│ (HAPI) │
└─────────┘
The FHIR server behind the façade is a choice of its own (open source, managed cloud or commercial); see FHIR servers compared for criteria such as profile validation, subscriptions and bulk operations.
How the translation works
An engine channel has three stages. The source connector receives the raw v2 message over MLLP/TCP. The transformer parses segments and builds FHIR resources. For an ADT^A01 it reads PID for demographics and PV1 for the visit:
HL7 v2 Segment.Field → FHIR Resource.Element
─────────────────────────────────────────────────────────
PID-3 (Patient ID) → Patient.identifier
PID-5 (Patient Name) → Patient.name
PID-7 (Date of Birth) → Patient.birthDate
PID-8 (Sex) → Patient.gender
PID-11 (Address) → Patient.address
PV1-2 (Patient Class) → Encounter.class
PV1-3 (Assigned Location) → Encounter.location
PV1-7 (Attending Doctor) → Encounter.participant
PV1-44 (Admit Date/Time) → Encounter.period.start
OBR-4 (Universal Service) → ServiceRequest.code
OBX-3 (Observation ID) → Observation.code
OBX-5 (Observation Value) → Observation.value
OBX-14 (Date/Time of Obs) → Observation.effectiveDateTime
The destination connector sends the resources to the FHIR server, usually as a transaction Bundle so that a message's resources succeed or fail together. The legacy systems do not know their messages are being translated, which keeps the approach additive.
Use the HL7 V2-to-FHIR guide as the mapping baseline
HL7 published the V2-to-FHIR implementation guide as version 1.0.0 (STU 1) in October 2025, based on FHIR R4. It provides ConceptMaps for messages, segments, data types and vocabulary, starting from v2.9 but covering older structures still seen in production. It also documents implementation questions you will hit anyway: MSH mapping to MessageHeader and Provenance, reusing one Practitioner referenced from several segments, repeating v2 fields that map to non-repeating FHIR elements, and Resource.id generation.
Treat it as the starting contract, then record your local deviations (Z-segments, national identifiers, site code tables) as explicit overrides. The guide maps the base standard, not national or vendor v2 profiles.
Terminology mapping
Structural mapping is the easy part. HL7 v2 messages often carry local codes that mean nothing outside the source system. A lab may send "GLU" for glucose where FHIR consumers expect LOINC 2345-7 (Glucose [Mass/volume] in Serum or Plasma). Maintain crosswalk tables as a shared resource:
- Local lab codes → LOINC for observations
- Local diagnosis codes → ICD-10 / SNOMED CT for conditions
- Local drug codes → ATC / national drug codes for medications
When a code is missing from the crosswalk, route the message to an exception queue for review. Never drop it silently or guess a mapping.
ACK/NACK and reliability
The receiver of each v2 message returns an ACK (accepted) or a negative acknowledgement. In the coexistence architecture:
- Acknowledge the source once the message is durably stored in the engine, so the source does not time out.
- Transform and deliver asynchronously: map to FHIR, then send to the FHIR server.
- Queue downstream failures separately: if the FHIR server is down, park the message in an error queue rather than returning a rejection that makes the source retry in a loop.
Dirty messages
Production v2 messages are rarely clean. Plan for:
- Line break inconsistencies: some systems use
\ninstead of the required\ras segment separator. Normalize before parsing. - Character encoding mismatches: MSH-18 declares the character set, but many systems ignore it. Accented names, common across Europe, get garbled.
- Non-standard Z-segments: map them deliberately or ignore them on purpose, and document which.
- Missing required fields: reject or quarantine incomplete records unless a documented, clinically approved derivation exists. Never fabricate required clinical values.
Migrating historical data to FHIR
Live feeds only cover what happens after go-live. Consumers usually also need history: past encounters, results and reports. A FHIR data migration is a batch job with different risks from the live feed.
- Identifiers first. Load Patient and Encounter with business identifiers (system plus value for each assigning authority) before dependent resources. Merges and links from the master patient index must be resolved, or history attaches to the wrong person.
- Conditional writes for idempotency. Use conditional update or create keyed on business identifiers so that a rerun updates instead of duplicating.
- Provenance. Record where each resource came from with Provenance (source system, extract date, mapping version). It is what lets you explain or reload a record later.
- Bulk mechanics. The FHIR Bulk Data Access specification standardises export, not import. Large loads use transaction or batch Bundles, or a server-specific import operation; test throughput and referential integrity on a full-size copy.
- Corrections and status. Historical results include amended and cancelled values. Map their statuses explicitly instead of loading only the latest value.
A historical load entry might look like this (de-identified):
{
"resourceType": "Bundle",
"type": "transaction",
"entry": [
{
"fullUrl": "urn:uuid:7f9c2b1e-0d4a-4c3e-9a51-2b8e6f0c1a01",
"resource": {
"resourceType": "Patient",
"identifier": [{ "system": "https://hospital.example.org/mrn", "value": "MRN-EXAMPLE-0001" }],
"name": [{ "family": "DOE", "given": ["JANE"] }],
"birthDate": "1977-04-12"
},
"request": {
"method": "PUT",
"url": "Patient?identifier=https://hospital.example.org/mrn|MRN-EXAMPLE-0001"
}
},
{
"resource": {
"resourceType": "Provenance",
"target": [{ "reference": "urn:uuid:7f9c2b1e-0d4a-4c3e-9a51-2b8e6f0c1a01" }],
"recorded": "2026-10-06T09:00:00Z",
"agent": [{ "who": { "display": "Historical load job v1.3 (example)" } }]
},
"request": { "method": "POST", "url": "Provenance" }
}
]
}
Testing and validation
- Profile validation: run every generated resource through a FHIR validator against the profiles you committed to (national base profiles, US Core, AU Core or your own).
- Mapping regression: keep a corpus of de-identified v2 messages per feed with expected FHIR output; rerun it on every mapping change. The V2-to-FHIR guide's test conversions are a useful seed.
- Reconciliation: compare daily counts per event type and per resource type, then sample records field by field.
- Clinical review: have clinicians check rendered results and reports, not only JSON.
- Failure drills: FHIR server outage, duplicate messages, out-of-order A08 updates, merges (A40) and cancelled orders.
When to retire a v2 feed
Switch off a v2 interface only when every consumer has moved, reconciliation has been stable for an agreed period, the rollback path is documented and archive access is settled. Retire feeds one at a time. The engine's role shifts from translator to router as more systems speak FHIR natively.
European interoperability programmes make standards-based exchange more relevant. Confirm the applicable implementation guides, national interfaces and phased requirements for each project; EHDS alone is not a universal mandate to expose a particular FHIR API. For French-speaking hospitals with HPRIM and INS constraints, the French-speaking hospitals guide covers the local inventory.
If you are planning an HL7 v2 migration and want help with the interface inventory, engine channels, FHIR mapping or historical load, see HL7 and FHIR integration services.