Mapping HL7 Version 2 to FHIR Messages

When thinking about how FHIR is going to be implemented ‘for real’, it’s likely that ‘green fields’ applications – where there is no existing standard in use – will be early adopters – mobile is an obvious example.

But inevitably, we’re going to need to manage situations where HL7 standards are already in use and we want to introduce FHIR as well – i.e. the two standards are going to co-exist.

There’s a lot of work underway with mapping CDA (and CCDA in particular) to FHIR resources – indeed this work will provide a valuable ‘peer review’ for the completeness of FHIR resources, but in this post let’s have a look at how we might introduce FHIR into an existing HL7 version 2 based infrastructure.

Now, this is a really big area to cover, so we’ll use a specific – and small – Use Case to guide our discussion – we’ll take an HL7 version 2.4 ORU message (technically an ORU^R01 containing a radiology result), and produce a FHIR message that we can submit to a FHIR server, which will then save the Observation in the data store.

To keep things reasonably simple (yet be realistic) we’ll have a single OBX segment with the result in it. In practice, we’d likely have multiple OBX segments – each of which would be a separate Observation resource in our message (plus NTE segments and others). And note that some of the segments aren’t used – we’re only pulling out the data we need to populate our message. If we were planning a ‘2 way’ transform – ie retaining the ability to recreate the v2 message from the FHIR one, then we’d probably need to capture more of the data – and likely need some extensions as well.

In this post we’re just going to consider the creation of the FHIR message containing the Radiology observation (and supporting resources) – we can consider the architecture and workflow aspects of subsequently processing the message in another post.

And remember that HL7 v2 can be used in many different ways (if you’ve seen one v2 implementation…) so this analysis is highly specific to this use case.

Here’s a sample of what the v2 message might look like:

MSH|^~\&|Amalga HIS|BUM|New Tester|MS|20111121103141||ORU^R01|2847970-201111211031|P|2.4|||AL|NE|764|ASCII|||
PID||100005056|100005056||Dasher^Mary^""^^""|""|19810813000000|F||CA|Street 1^""^""^""^34000^SGP^^""~""^""^""^""^Danling Street 5th^THA^^""||326-2275^PRN^PH^^66^675~476-5059^ORN^CP^^66^359~-000-9999^ORN^FX^^66^222~^NET^X.400^a@a.a~^NET^X.400^|123456789^WPN^PH^^66|UNK|S|BUD||BP000111899|D99999^""||CA|Bangkok|||THA||THA|""|N
PV1||OPD   ||||""^""^""||||CNSLT|||||C|VIP|||6262618|PB1||||||||||||||||||||||||20101208134638
OBR|1|""|BMC1102771601|""^Brain (CT)||20111028124215||||||||||||||||||CTSCAN|F||^^^^^ROUTINE|||""||||||""|||||||||||^""
OBX|1|FT|""^Brain (CT)||++++ text of report goes here +++|||REQAT|||F|||20111121103040||75929^Gosselin^Angelina

So there are 7 segments in our incoming HL7 v2 message:

Segment Purpose FHIR Resource
MSH Message header MessageHeader
PID Patient Identification Patient
PV1 Patient Visit Not used in this example
PV2 Patient Visit – Additional data Not used in this example
ORC Common Order Not used in this example
OBR Observation Request Observation
OBX Observation ObservationProvider


Heres what our FHIR bundle will look like:

  • MessageHeader
  • Observation
  • Patient (Subject of the Observation)
  • Provider (Performer of the Observation)

As a basis of the analysis we’ll the mappings that are in the FHIR specification (at the top of each resource page there are tabs for the content, examples, formal definitions, mappings & profiles )

So here’s how we can create each resource – with some notes about the choices we need to make.


The bundle is an atom feed, and the id of the bundle is a globally unique ID (eg a UUID) that represents the FHIR message as a whole. This is distinct to the MessageHeader ID which is important when dealing with errors that occur during messaging (see the spec for a discussion of this).


A FHIR message is a bundle of resources, with the MessageHeader resource as the first resource (and a tag on the bundle) . There’s a pretty good mapping between MSH and MessageHeader:

Element V2 segment Description
Identifier MSH-10 Message Control ID
Timestamp MSH-7 Message Date/time
Event MSH-9.2 observation-provideDerived from the second component of the Message Type field. Its value comes from HL7 table 3 MSH-3 Sending application name MSH-3  Sending application name
Source.endpoint MSH-24 Sending network address MSH-5 Receiving application
Destination.endpoint MSH-25 Receiving network address
data References to the ‘root’ resource of the message.

Some notes.

  • The identifier is set by the sending system, and should be unique in the context of the sender. Ideally globally unique, but there’s no guarantee of that from the v2 side. From FHIRs perspective, it needs to be unique within ‘the stream of messages’, in practice we’d expect it to be unique in the context of the sender – after all, it’s how they will be matching acknowledgements to the message.
  • The software element is is a required field, and the mapping in the spec suggests using the SFT segment. However, that was only introduced in version 2.5, so we’ll duplicate the here.
  • The event code is defined in the FHIR spec (Theres a more complete list here). These are not the same as the v2 ones – though as the ‘strength’ of the binding is we could use the v2 ones if we wanted to. However, theres a value that suits this implementation – observation-provide – so we’ll use that.
  • The enterer, author and receiver elements allow us to specify individuals (or organizations) from whom the message originated, or who it is for. We don’t need that here, but nice to know it’s available if needed.
  • In this case the data element will be a reference to the Observation. If we had multiple observations, then we’d probably include a List resource to group them together. And, of course, a message can get a lot more complicated than that.


This is the resource that holds the actual thing that we want to represent – in this case a radiology result – and Observation is one of the most flexible resources in the FHIR stable.

Here’s the mapping summary table:

Element V2 segment comment
name OBX-3
valueString OBX-5 OBX-5 holds the actual value of the observation
interpretation OBX-8
comments NTE-3 See note below
appliesDateTime OBX-14
issued OBR.22
status OBX-11 Observation Result Status
reliability OBR-25
identifier OBX-21
subject Reference to the Patient
performer Reference to the Practitioner

Some notes:

  • The name (or type) of observation (a radiology result) is stored in OBX-3. It’s a CE datatype in v2, which maps to a CodeableConcept in FHIR
  • The value of the observation will be in the Observation.value[x] element, where [x] is one of the specified FHIR datatypes. In v2, there are at least 3 fields that we’d look at to decide how to get the value.
    • OBX-2 is the type of the Observation as defined in the HL7 v2 table 0125. For our radiology result, this will likely be FT or ST and the type of the value[x] element will be a string – making the full element name valueString. If we were building a generic parser, then we’d look at the value of OBX-2 and use that to decide what FHIR data type to use.
    • OBX-5 holds the actual value of the observation. The details of this will depend on the datatype as discussed above of course…
    • OBX-6 is the units of the value. Doesn’t apply here but in many situations it will. For example, if this was a Systolic Blood Pressure then the datatype would be Quantity, which has a units component to it.
  • According to the mapping in the spec, when the report was issued, can come from a number of different places depending on the exact source and nature of the message. We’ll go with OBR-22 (Status change date/time) for our scenario.
  • The reliability of the result is a bit challenging – and it’s a required element in the Observation resource so we can’t ignore it. The mapping in the spec refers to OBX-8 (Abnormal Flags) and OBX-9 (Probability) – but these are more a reflection of the result within the expected range, rather than whether the result can be trusted. OBR-25 (Result Status) seems a better choice, so we’ll go with that.
  • If there was a comment about the result, then it would be stored in a separate NTE segment (or segments) immediately after the OBX. There isn’t a direct reference in v2 from the OBX to the NTE (or the other way round) – you have to infer that from the fact that the NTE follows the OBX.
  • We also need to think about what the Observation.text – the human readable narrative should be. As this is a string datatype – and assuming that the v2 datatype (OBX-2) was FT, then we’ll enclose the whole value in <pre> elements, and perhaps add the date and author names there as well. I suspect that a proper consideration of how to generate narrative is a full topic in and of itself – perhaps another time.



In FHIR, the Patient is a separate resource (maybe not even on the same server as the observation), and the Observation will have a reference to it (as the subject). So we need to find the Patient in whatever server it is stored and retrieve its url. We might also need to create the Patient if we can’t find it – though this will depend on the policies of the implementation, which might require that the patient exist first.

The way we’ll search for a patient will be using the patient identifier from the PID segment. PID-3 has a list of patient identifiers that we can use – each being a CX datatype, so equivalent to FHIRs identifier datatype. We need to query the Patient server to see if the Patient already exists, using the appropriate identifier (based on the namespace). If it exists, then we have the reference. If not then we can use the data in the PID segment to create a new Patient resource – if policy allows. Here are the steps:

Using the identifier from the v2 message, query the FHIR server as follows:

GET [patientserver]/patient?identifier={identifier}
  • If there’s a single match then we have the ID that we can use for the Observation.subject. (And we can place a copy of the Patient inside the bundle as well)
  • If there is more than one match, then that’s probably an error and we should reject the message or get human intervention (again, there will be some policy around this).
  • If there are no matches, then the next step depends on whether the patient store is on the same server as the observation.
    • If it is, then create a new Patient resource using the data from the PID segment, and give it an ID with a cid: prefix (which will tell the server that it’s a new Patient resource and it will save it locally – resolving the reference to the Observation as it does so).
    • If the patient is on a separate server, then we need to create and save a patient now, getting an ID back from the server as it is saved. We can then place the patient resource in the message (along with the correct ID). Of course there are transactional concerns here: what if we save the patient, but the rest of the message processing fails? Well, it doesn’t really matter – the next time a message comes through with this patient identifier we’ll find the one we just created, so no harm done.

Note that it’s possible to offload all this lookup to the server by using the internal search facility of a transaction (we talked about that here) – but I’m not sure that functionality applies for message processing, and in any case we can’t assume that all servers will have that functionality, so we’ll do it the long way.

And also note that we are assuming that the server processing the message has the ability to create Patient resources locally. If not, then we’ll need to create the Patient as a separate step as we did with a remote Patient server. (We’ll think about message processing in more detail when we think about workflow in another post)


The observation has a performer element which is the person/device who performed the observation. In our case this will be a Provider though a device is another common use – we’ll think about the implications of that in another post.

The same considerations will apply to the Provider as it did for the Patient in terms of finding a resource and providing it as a reference to the Observation, but there are a couple of extra hooks:

  • There are more possible locations in the v2 message where we can get ‘performer’ information – and which one we choose will depend on the type of message (check out the mapping in the spec for some of the possibilities).
  • And depending on which one we choose – we’ll likely have less information about the provider in our message, making it harder to create one if it doesn’t already exist in our system (and the local policy allows this)

In our case we’ll choose OBX-16 – responsible observer. This has the added advantage that the v2 datatype for this field is XCN (Extended Composite ID Number and Name for Persons) –which means that we should have enough detail to create a provider resource if we need to (provided that all the fields in the message are populated of course).

Last Words

When I started this exercise, I assumed that the mapping exercise would be straightforward – and at a high level it is reasonably so. However, like all v2 implementations there is a considerable amount of choice when mapping – perhaps not too surprising given the complexity of healthcare. Even if vendors provide tooling with ‘standard’ mappings’  and automated narrative generation, I expect that most implementations are going to require some ‘tweaking’ of those mappings for individual resources…

And many of the choices made in this post can certainly be challenged!

The question of ‘policy’ arose quite a bit – what should systems do in certain circumstances like new Patient’s or where there are multiple patients with the same identifier or what to do when a Provider can’t be found – is also rather more important than I had anticipated.

Which reminds me of Grahames second law (he’s obviously a fan of Asimov) You can move complexity around, but you can’t make it go away.

About David Hay
I'm an independent contractor working with a number of Organizations in the health IT space. I'm an HL7 Fellow, Chair Emeritus of HL7 New Zealand and a co-chair of the FHIR Management Group. I have a keen interest in health IT, especially health interoperability with HL7 and the FHIR standard. I'm the author of a FHIR training and design tool - clinFHIR - which is sponsored by InterSystems Ltd.

25 Responses to Mapping HL7 Version 2 to FHIR Messages

  1. Pingback: FHIR Messages – part 2 | Hay on FHIR

  2. David, excellent post

  3. Shamil says:

    OK, since everyone is so smart here I’ll ask a silly question – we did mapping, we located a patient, so where is the final bundle example and what should I do with that?

    • David Hay says:

      Hi Shamil – not quite sure I follow you. There’s a picture of what the bundle would look like in the post (it’s not an XML example, rather the resources – which includes the observation and practitioner – are depicted as boxes) – were you looking for a complete XML type example?

      btw – I note in passing that I called ‘Practitioner’ as ‘Provider’ – that was an error on my part…

      • Shamil says:

        Right, in this particular case, I would like to see an actual bundle (in json or xml) with all details required by Atom standard rather than a picture. To give a sense, it’s like showing v3 RMIM for message type telling that this is how your message may look like.

      • David Hay says:

        Shamil – my apologies for the delay in replying. We looked at a sample bundle when we were thinking about encounters – here is the reference. The same overall layout would apply for v2 messages, with different resources of course.

        However, you should be aware that in DSTU-2 it is proposed to replace the Atom format with a custom resource (called a Bundle, oddly enough). You can read about that here:


  4. Barry Runyon says:

    Very informative post. Explains things clearly. David – do you know if there are any industry standards or best practices for the retention of HL7 messages?

    • David Hay says:

      Thanks Barry. I’m not aware of any specific recommendations about retention of messages after they have been processed – the nature of a message (as opposed to a Document) is that they are supposed to be ephemeral – you can throw them away after they have been processed by the recipient system (other than any retention as part of audit purposes of course). Rene Spronk authored an article on this here:

      Having said that, my own employer – Orion Health – is moving to a model where we will store all messages indefinitely, which has a number of advantages from the ability to ‘re-play’ them if required, through to the ability to use them as the source of new models long after they have been received.


      • Susan E. Campbell says:

        Does the bundle have anything to do with care bundles or payment bundles? Or is it a homonym thereof?

      • David Hay says:

        Hi Susan – as far as I am aware it just sounds the same. In dstu-1 it is an atom feed, in dstu2 it is a discrete resource, but it’s purpose is just to be able create a single ‘thing’ that can group together a collection of resources…


  5. Pingback: The Architecture and the (FHIR) Message | Hay on FHIR

  6. Rene Spronk says:

    I agree that policy decisions have a big impact on how one does message processing. Whether one accepts patient resource updates from a non-reliable source such as a Laboratory has an enormous impact on message processing. Even in production HL7v2 interfaces there are a lot of implicit rules about message processing.

    Given that the interest in messaging within the FHIR context is relatively low we probably won’t see any messaging capable FHIR Servers for a while, which means it’ll be up to the communication servers to transfrom v2-messages into REST based FGIR resource exchanges. Which also means the communication server will have to deal with the policies.

    A detail on the MessageHeader: I tried to do a mapping as well, and ended up (using DSTU2) mapping processing mode (P/T production/test) to a resource meta/tag.

  7. siva says:

    Do all segments and mandatory fields of HL7 message can be covered in fhir resources

    • David Hay says:

      Well – I hesitate to say ‘all’ – but most should be covered. FHIR (as an implementer focused standard) is quite nicely aligned to HL7 v2…

      • hcm0303 says:

        There are several fields that are not mapped to FHIR resources. (like PID-4, PID-10 and PID-12 etc. I browsed whole resource documentations from FHIR website) How can I save whole HL7 message segments when I get the message that contains those unmapped fields?

  8. Kirby says:

    Great article! For a system that currently does not support interfaces, would it make sense to use FHIR DTSU 2 as the foundation and create HL7 v2.x interfaces that interact with FHIR to support legacy clients? I have a need to consume patient demographics and send scheduling information out of a application. The thought was to use FHIR as the foundation so we could support the need for FHIR implementations when the arise.

    • David Hay says:

      Sure – putting FHIR interfaces in front of existing systems is a very common architecture. Often supporting REST rather than messaging at the moment though…

      Whether to use STU-2 or 3 depends on the specific environment. Where there is not an existing FHIR infrastructure to conform to, I’d suggest using STU-3, which should be available in Q1 next year. But, of course, if you need to interact with STU-2 based systems then that might not be an option right now…


  9. mvelascomtz says:

    Very helpful. Thank you

  10. Hi David.
    What comes around comes around it seems.
    I am doing a little POC that that does the opposite. I am consulting at the largest payer org in the US. I have been working with Mirth_Connect for years. It has a excellent mapping engine.

  11. chris_vr says:

    I am working HL7 v2.3 v2.6 version data …how do i store these data. each OBX and OBR has many fields around.what is best way store theses value.these should query able also.Is converting HL7 message to FHIR format is best strategy.If yes is relation database like postgres sql is the best way to store it?

  12. Eva says:

    Thank you for the post.
    My question is: What is the use case, when you need FHIR observation versus original Hl7 v2 ORU?
    What does it enable and why is it required?

    Thank you

Leave a Reply

%d bloggers like this: