HL7 v2 vs FHIR comes down to what the receiving system accepts. HL7 v2 sends pipe-delimited messages, usually over a socket to an interface engine, and it is widely used in hospitals. FHIR sends JSON or XML resources over a REST API. For extracted document data, send whichever format the receiver supports well, and prefer FHIR when both work.
HL7 v2 messages and FHIR resources side by side
An HL7 v2 message is a sequence of segments, one per line, each a list of fields split by a pipe character. A FHIR resource is a self-contained JSON or XML object with named elements. Both carry the same lab result; the difference is that v2 segments only make sense inside their message, while FHIR resources stand alone.
The FHIR R4 comparison with HL7 v2 puts it plainly. HL7 v2 uses messages composed of reusable segments, and its segment structure roughly corresponds to FHIR's idea of resources. However, v2 segments can't be independently manipulated, while FHIR focuses on objects that can be maintained independently. In v2, you cannot fetch or update one OBX segment on its own. In FHIR, you can read or update one Observation by its ID.
One fictional lab result in both formats
Here is one made-up potassium result for a made-up patient, Avery Sample, as an HL7 v2 ORU^R01 message. Every identifier and value is a fictional example.
MSH|^~\&|EXAMPLE_LAB|EXAMPLE_FAC|EXAMPLE_EHR|EXAMPLE_CLINIC|20260914134200||ORU^R01|MSG00001|P|2.5.1
PID|1||MRN-EX-0042^^^EXAMPLE||Sample^Avery
OBR|1||LAB-EX-7781|24320-4^Basic metabolic panel^LN|||20260914081000
OBX|1|NM|2823-3^Potassium [Moles/volume] in Serum or Plasma^LN||5.4|mmol/L|3.5-5.1|H|||F
The HL7 Version 2 to FHIR implementation guide maps each of these fields. In its OBX to Observation map, OBX-3 becomes code, OBX-2 decides which value[x] type OBX-5 fills (NM becomes valueQuantity), OBX-6 supplies the unit, OBX-7 becomes referenceRange.text, OBX-8 becomes interpretation and OBX-11 becomes status. The FHIR v2 status table defines F as final results. Its MSH map notes that the field separator (MSH-1) and encoding characters (MSH-2) are not used in FHIR at all.
Versions add another layer to the v2 side. The example declares version 2.5.1 in MSH-12, the version ID field. The v2 to FHIR guide says it is not tied to one v2 version: it starts from v2.9 but also covers components that were deprecated yet still appear in messages built on older versions. A receiver's interface specification will name the version it expects, and fields can shift meaning between versions, so the version is the first thing to agree on. FHIR has releases too, and every example in this post uses R4, the release the v2 to FHIR guide is built on.
The same result as a FHIR Observation is a JSON object with named elements: status, code, subject, effectiveDateTime, valueQuantity, referenceRange and interpretation. The full annotated version is in the guide to one lab result as an Observation, worked example, so it is not repeated here.
| Aspect | HL7 v2 | FHIR |
|---|---|---|
| Unit of exchange | A message of segments (MSH, PID, OBR, OBX) | A resource, or a Bundle of resources |
| Encoding | Pipe-delimited text, positions carry meaning | JSON or XML, named elements |
| Can one piece be read alone? | No, segments live inside a message | Yes, each resource has its own ID |
| Typical transport | MLLP over a socket, or files | REST over HTTPS |
| Extension | Z-segments | Extensions identified by URI |
| Tooling | Interface engines, segment parsers | REST clients, validators, profiles |
| Typical receivers | Hospital EHRs, lab and billing interfaces | Newer EHR APIs, apps, payer and patient data services |
Which HL7 v2 message types carry document-derived data?
Four HL7 v2 message types matter for data extracted from paperwork. ORU^R01 carries results such as lab values. MDM^T02 carries a document and its status. ADT messages carry patient demographics and visits. DFT^P03 carries financial transactions such as charges. Which one fits depends on the document you read.
The v2 to FHIR message map index lists ORU_R01, MDM_T02 (document status change notification and content), ADT messages such as ADT_A01 (admit or visit notification), and others. DFT_P03 is not in that index. It appears as the detail financial transaction in the message structure list on the HL7 Version 2+ demo site, a draft rendering of a future v2 standard rather than a published release, so confirm the structure against the v2 version your receiver names.
Matching document types to messages is mostly common sense:
- Lab reports. Discrete results go in ORU^R01: PID for the patient, OBR for the order or panel, one OBX per result, and optionally SPM for the specimen. The ORU_R01 to Bundle map shows that structure.
- Discharge summaries and notes. When the receiver wants the document itself, MDM^T02 carries it. The v2 to FHIR guide maps the TXA document header segment to a DocumentReference.
- Intake forms. Demographics and insurance from an intake form fit ADT messages, where PID carries the identifier list (PID-3) and name (PID-5), per the PID to Patient map.
- Charges. DFT^P03 is the v2 route for financial transactions. Payer remittances are a different case: they arrive as X12 835 files, not HL7 messages, covered in X12 835 parsing.
ORU deserves a caution. A lab that already sends ORU messages is sending the structured truth. A PDF of the same report is a rendering of it. How the two compare as sources is the subject of HL7 ORU messages vs PDF lab reports.
When does HL7 v2 beat FHIR for an extracted-data feed?
HL7 v2 beats FHIR when the receiving system has a working v2 interface and a weak or missing FHIR write API. If a hospital system already takes results through an interface engine that speaks ORU, and its FHIR endpoint cannot accept writes for the resources you need, v2 is the faster and safer path.
The FHIR comparison page notes that HL7 v2 is one of HL7's most widely adopted standards and is prominent in inpatient settings throughout the world. That reach is the main argument for v2. A receiver that already runs v2 interfaces has people and tools that know the format, so a new inbound feed is familiar work for them.
FHIR wins in other cases. It wins when the receiver offers a FHIR API that accepts writes, when the data has to be read back by apps, and when the receiver is not a hospital system at all. It also wins on validation: the comparison page says FHIR profiles are built into the tooling, while it describes the v2 mechanism for this as not widely used.
Three questions settle most choices:
- What does the receiver accept for writes? Ask for its interface specification. A read-only FHIR API does not help an inbound feed.
- Who maintains the mapping? A v2 feed often depends on the receiver's interface team. A FHIR feed puts more of the mapping in your code.
- Does anyone need to query the data later? FHIR resources can be fetched one by one. v2 messages are consumed and gone.
Broader options for getting document data into an EHR, including manual entry and file imports, are compared in the guide to EHR integration options for document data.
Schema Care turns lab reports, intake forms and other paperwork into structured fields that a person reviews when confidence is low, as the human review workflow on the homepage shows. Schema Care offers no API, SDK, webhooks, FHIR output, 835 output or 837 output, and it emits no HL7 v2 messages either; a team that wants v2 or FHIR would build that step on top of the reviewed fields.
HL7 v2 versus FHIR transport: MLLP, files and REST
HL7 v2 messages usually travel over MLLP, a thin framing layer on a network connection, or as batch files. FHIR resources travel over a REST API using HTTPS, with each resource type at its own URL. The transport shapes your error handling, retry logic and network setup as much as the format does.
MLLP is minimal by design. The minimal lower layer protocol chapter on the HL7 Version 2+ demo site, a draft rather than a published release, frames each message with a start block byte of 0x0B, then the HL7 content, then an end block byte of 0x1C followed by a carriage return, 0x0D. That is all the framing there is. Check the byte values against your receiver's interface specification before you rely on them.
File transport is the fallback when no socket is available: messages are written to a shared folder or sent by secure file transfer, and the receiver picks them up on a schedule. It is slower, and errors surface later, but it suits batch loads of backlogged documents.
FHIR uses ordinary web requests. The FHIR R4 RESTful API page describes interactions such as read, create and search, written as VERB [base]/[type]/[id], with JSON sent as application/fhir+json. Creating an Observation is a POST to [base]/Observation. The response tells you at once whether the server accepted it and what ID it assigned.
The practical differences:
- Network. An MLLP feed is a direct connection between two systems that someone has to set up and keep open. FHIR runs over HTTPS like any web API.
- Errors. MLLP framing carries no error information of its own, so problems are reported by whatever reply the receiver's interface sends back. A FHIR request gets an ordinary HTTP response for each call.
- Ordering. MLLP feeds are usually sent one message at a time, in order. FHIR requests can run in parallel, so your code has to handle ordering when it matters.
The HL7 v2 trap of custom Z-segments
A Z-segment is a site-defined HL7 v2 segment whose name starts with Z. Interfaces add them to carry data the standard segments lack. The trap is that every Z-segment is private: its meaning lives in one site's documentation, so each new receiver means new mapping work and new testing.
The FHIR comparison page says HL7 v2 provides an extensibility mechanism through Z-segments, and that the meaning of these extensions is opaque without prior manual explanation by the sender. That is the cost in one sentence. Nothing in the message tells a parser what ZEX-3 holds. Someone has to read a specification, if one exists, and write code for that site.
Extracted document data invites Z-segments because paperwork carries fields the standard segments do not, such as a confidence score, a source page number or a reviewer's name. Putting those in a custom segment works for one receiver. The second receiver will want them somewhere else, or not at all.
FHIR handles the same need differently. The comparison page says FHIR extensions can appear at any level, including inside data types, and that identifying them by URI keeps extensions from independent systems from colliding. It also suggests that when converting a Z-segment to FHIR, the extension URI should be specific to the system that defined the segment. An extension is still custom, but it is self-identifying and can be described in a published profile.
Ways to keep Z-segment cost down:
- Ask first. Many receivers already have a field for the data you want to send, such as an NTE note segment for comments.
- Leave metadata out. Confidence scores and page links are useful for review, but most receivers do not need them once a person has confirmed the value.
- Document every Z-segment. If you must add one, publish its field list and keep it versioned.
When the receiver expects whole clinical documents rather than messages or discrete resources, the choice shifts again, as the comparison of C-CDA vs FHIR explains.