Skip to main content
v2026.11,858 entries · CC-BY 4.0
LAC HealthMedical & Research SuppliesReagents, PPE & instruments — chain-of-custody documented.Fast, traceable sourcing built for regulated research environments, from bench consumables to instrumentation.Shop lac.us CodeCASRAIlac.us

HL7 Parser Guide: How to Read and Parse HL7 v2 Messages, MLLP Framing, ACK/NAK Codes, and Open-Source Libraries (Python, Java HAPI & .NET NHapi)

Practical engineering guide to reading and parsing HL7 v2 messages: ER7 pipe-and-hat delimiters, MSH-1 indexing rules, MLLP socket framing, ACK/NAK choreography, and open-source HL7 parsers in Python (python-hl7, HL7apy), Java (HAPI), and .NET (NHapi).

Written and maintained by CASRAI Editorial Board

Last updated

An HL7 parser is a software library or interface-engine component that tokenizes, validates, and transforms pipe-delimited HL7 Version 2 (HL7 v2.x) clinical interface messages into strongly typed programmatic objects, relational database rows, or HL7 FHIR resources. Although modern APIs increasingly adopt RESTful JSON via FHIR R4 (and Java developers use HAPI FHIR for FHIR resources), the HL7 v2 standard remains the operational backbone of hospital Admission-Discharge-Transfer (`ADT`), laboratory order/result (`ORM`/`ORU`), pharmacy (`RDE`), and immunization (`VXU`) feeds across real-world EHR integration pipelines.

How Do You Read and Parse a Sample HL7 v2 Message? Delimiters and the `MSH-1` Indexing Rule

An HL7 v2 message in standard ER7 (Encoding Rules 7 / pipe-and-hat) format is an ASCII/UTF-8 text stream divided into lines called Segments, each terminated by a single carriage return (`r`, ASCII hex `0x0D` — not a standard Windows `rn` or Unix `n` newline). Every message starts with the mandatory `MSH` (Message Header) segment, which dynamically defines the five delimiter characters used throughout the rest of that message:

Hierarchy Level Default Character Defined In Escape Sequence Structural Role & Example
Segment Terminator r (`0x0D`) HL7 v2 Control Spec .br (line break inside text) Separates segments (`MSH`, `PID`, `PV1`, `OBR`, `OBX`)
Field Separator | (Pipe) MSH-1 (4th byte of message) F Separates numbered fields inside a segment (`PID-3`, `PID-5`)
Component Separator ^ (Hat / Caret) MSH-2 (1st char) S Splits composite data types (`EVERYWOMAN^EVE^E` = Family^Given^Middle)
Repetition Separator ~ (Tilde) MSH-2 (2nd char) R Separates repeating values inside a single field (multiple MRNs or phone numbers in `PID-3`)
Escape Character (Backslash) MSH-2 (3rd char) E Escapes literal delimiter characters inside clinical text notes
Subcomponent Separator & (Ampersand) MSH-2 (4th char) T Splits nested subcomponents inside a component (`AssigningAuthority&OID&ISO`)

The Classic HL7 v2 Parser Trap: `MSH-1` Off-by-One Indexing

Every engineer writing a custom string-splitting HL7 parser encounters the `MSH-1` off-by-one rule: in the `MSH` segment only, `MSH-1` is the literal pipe character (`|`) immediately following the letters `MSH`, and `MSH-2` is the encoding character string (`^~&`). Consequently, if you split an `MSH` line on `|`, array index `1` (`^~&`) corresponds to `MSH-2`, array index `2` corresponds to `MSH-3` (Sending Application), and array index `8` (`ORU^R01`) corresponds to `MSH-9` (Message Type). In every other segment (`PID`, `PV1`, `ORC`, `OBR`, `OBX`), field 1 (`PID-1`, `OBX-1`) is the string between the first and second pipe characters.

Annotated Sample HL7 v2.5.1 `ORU^R01` Laboratory Result Message

MSH|^~&|EPIC_LAB|UNIV_HOSP|REDCAP_EDC|CLIN_TRIAL|20261109093015||ORU^R01^ORU_R01|MSG20261109-0042|P|2.5.1
PID|1||MRN994821^^^UNIV_HOSP^MR~NCT05512345-019^^^TRIAL^RI||DOE^JANE^MARIE^^^^L||19740822|F
PV1|1|O|CARDIOLOGY^RM204^BED1||||1234567890^SMITH^ROBERT^A^^^MD^^NPI
OBR|1|ORD-88210^EPIC_OE|ACC-44109^EPIC_LAB|2345-7^Glucose [Mass/volume] in Serum or Plasma^LN|||20261109084500
OBX|1|NM|2345-7^Glucose [Mass/volume] in Serum or Plasma^LN|1|184|mg/dL^milligrams per deciliter^UCUM|70-99|H|||F|||20261109092500
  • `MSH` (Message Header): `MSH-3` = `EPIC_LAB` (Sending App); `MSH-9` = `ORU^R01^ORU_R01` (Unsolicited Observation Result); `MSH-10` = `MSG20261109-0042` (Message Control ID used for ACK correlation); `MSH-11` = `P` (Production, vs. `T` Training or `D` Debugging); `MSH-12` = `2.5.1` (HL7 Version).
  • `PID` (Patient Identification): `PID-3` contains two tilde-separated (`~`) identifier repetitions: hospital Medical Record Number `MRN994821` (`MR`) and Research Study Subject ID `NCT05512345-019` (`RI`); `PID-5` = `DOE^JANE^MARIE` (`Family^Given^Middle`); `PID-7` = `19740822` (DOB in `YYYYMMDD`); `PID-8` = `F` (Administrative Sex).
  • `OBR` (Observation Request) & `OBX` (Observation/Result): `OBX-2` = `NM` (Numeric data type, vs. `ST` String, `TX` Text, `CE`/`CWE` Coded Element, or `ED` Encapsulated Data/PDF); `OBX-3` = `2345-7^Glucose [Mass/volume] in Serum or Plasma^LN` (LOINC code `2345-7`); `OBX-5` = `184` (Value); `OBX-6` = `mg/dL` (UCUM Units); `OBX-7` = `70-99` (Reference Range); `OBX-8` = `H` (High abnormal flag); `OBX-11` = `F` (Final result status, vs. `P` Preliminary or `C` Corrected).

How Are HL7 Messages Transmitted and How Do HL7 `ACK` and `NAK` Messages Work?

Over local hospital networks and VPN tunnels, HL7 v2 messages are transmitted across persistent TCP/IP sockets using the Minimal Lower Layer Protocol (MLLP) framing envelope so the receiving socket parser can detect exactly where each message begins and ends:

<VT> (0x0B)  +  [HL7 v2 ER7 Message Bytes (Segments separated by 0x0D)]  +  <FS> (0x1C)  +  <CR> (0x0D)

For every MLLP frame received, the listener parses the message and returns a synchronous `ACK` (General Acknowledgment) message containing an `MSH` header, an `MSA` (Message Acknowledgment) segment, and—if parsing or validation failed—an `ERR` segment:

MSH|^~&|REDCAP_EDC|CLIN_TRIAL|EPIC_LAB|UNIV_HOSP|20261109093016||ACK^R01^ACK|ACK20261109-0042|P|2.5.1
MSA|AA|MSG20261109-0042|Message processed successfully
  • `AA` (Application Accept): Message parsed, validated, and committed. The sender dequeues the message and sends the next frame in its FIFO queue.
  • `AE` (Application Error — “Soft NAK”): The receiving application encountered a processing or business-logic error (e.g., transient database lock or unmapped code). Depending on interface engine retry policy, the sender alerts operations or retries with backoff.
  • `AR` (Application Reject — “Hard NAK”): The message failed structural validation (e.g., missing `MSH`, unsupported `MSH-12` version, or corrupted delimiters) and should be routed to an error queue rather than retried unmodified.
  • Enhanced Mode Commit Codes (`CA`, `CE`, `CR`): When `MSH-15` (Accept Acknowledgment Type) requests enhanced transport-level acknowledgment, the receiver immediately returns `CA` (Commit Accept) once the raw message is safely persisted to its inbound journal before downstream application parsing.

Open-Source HL7 Parsers and Viewers Compared: Python, Java (`HAPI`), and .NET (`NHapi`)

Rather than writing regex or `.split(‘|’)` scripts that break on escaped delimiters (`F`, `S`, `T`) or repeating fields (`~`), clinical data engineers rely on battle-tested open-source HL7 v2 parsers and GUI viewers:

Library / Tool Language / Platform License Schema Validation & Key Capabilities
python-hl7 Python 3.x BSD Lightweight container/list parser (`hl7.parse`), field/component indexing, and built-in `asyncio` MLLP client/server streams (`hl7.mllp`)
HL7apy (CRS4) Python 3.x MIT Specification-aware object model validating official HL7 v2.1–v2.8.2 message structures, data types (`CX`, `XPN`, `CE`), and semantic attribute names
HAPI (hapi-hl7v2) Java (JVM) MPL 1.1 / GPL Industry reference Java library: `PipeParser`, XML encoding, `Terser` path extraction (`/PID-5-1`), conformance profiles, and `SimpleServer` MLLP listener
NHapi C# / .NET Standard MPL Direct .NET port of Java HAPI providing strongly typed C# classes (`ORU_R01`, `ADT_A01`), `PipeParser`, and `Terser` navigation
HAPI TestPanel & Mirth Connect (NextGen Connect) Cross-Platform GUI / Interface Engine Open-Source / MPL Interactive HL7 v2 tree viewer, syntax highlighter, MLLP test sender/listener, and channel transformation engine

Working Python Example: Parsing `ORU^R01` with `python-hl7` and `HL7apy`

import hl7
from hl7apy.parser import parse_message

raw_hl7 = (
    "MSH|^~\&|EPIC_LAB|UNIV_HOSP|REDCAP_EDC|CLIN_TRIAL|20261109093015||ORU^R01|MSG0042|P|2.5.1r"
    "PID|1||MRN994821^^^UNIV_HOSP^MR||DOE^JANE^MARIE||19740822|Fr"
    "OBR|1|ORD-88210|ACC-44109|2345-7^Glucose^LN|||20261109084500r"
    "OBX|1|NM|2345-7^Glucose^LN|1|184|mg/dL|70-99|H|||Fr"
)

# 1. Lightweight index-based extraction with python-hl7
msg = hl7.parse(raw_hl7)
mrn          = str(msg.segment("PID")[3][0][0])   # 'MRN994821'
family_name  = str(msg.segment("PID")[5][0][0])   # 'DOE'
loinc_code   = str(msg.segment("OBX")[3][0][0])   # '2345-7'
glucose_val  = float(str(msg.segment("OBX")[5]))  # 184.0

# 2. Schema-aware semantic extraction with HL7apy
parsed = parse_message(raw_hl7, find_groups=False)
control_id = parsed.msh.msh_10.value              # 'MSG0042'
msg_type   = parsed.msh.msh_9.msh_9_1.value       # 'ORU'
print(f"[{control_id}] {msg_type}: Patient {family_name} ({mrn}) LOINC {loinc_code} = {glucose_val}")

Frequently Asked Questions

How do you read and parse a sample HL7 v2 message?

An HL7 v2 message encoded in ER7 (pipe-and-hat) format consists of carriage-return-delimited (`0x0D`) segments beginning with a 3-character segment ID (such as `MSH`, `PID`, `PV1`, `OBR`, or `OBX`). Within each segment, fields are separated by pipes (`|`), components by hats (`^`), repetitions by tildes (`~`), escape sequences by backslashes (“), and subcomponents by ampersands (`&`), as declared in `MSH-1` and `MSH-2`. Note the critical `MSH` indexing rule: `MSH-1` is the literal pipe character immediately following `MSH`, and `MSH-2` is the four encoding characters `^~&`, whereas in all other segments (like `PID` or `OBX`) field 1 is the text between the first and second pipes.

How do HL7 ACK and NAK acknowledgement messages work?

When an EHR or integration engine transmits an HL7 v2 message over Minimal Lower Layer Protocol (MLLP), the receiving system returns a synchronous `ACK` (General Acknowledgment) message containing an `MSH` header, an `MSA` (Message Acknowledgment) segment, and optional `ERR` (Error) segments. `MSA-1` carries the 2-character acknowledgment code—`AA` (Application Accept), `AE` (Application Error, often called a soft NAK requiring retry or investigation), or `AR` (Application Reject, a hard NAK for malformed syntax or unsupported versions)—and `MSA-2` echoes the exact Message Control ID from the sender’s `MSH-10`.

What open-source HL7 parsers and viewers exist for Python, Java, and .NET?

For Python, `python-hl7` provides lightweight 0/1-indexed list parsing and `asyncio` MLLP socket streams, while `HL7apy` provides schema-aware validation and semantic field lookups across HL7 v2.1–v2.8.2. For Java, HAPI (HL7 Application Programming Interface, `hapi-hl7v2`) is the enterprise reference library supporting `PipeParser`, `Terser` path queries, and MLLP servers; its sister project HAPI TestPanel is a widely used desktop HL7 viewer and MLLP simulator. For C# and .NET, NHapi is the direct strongly typed port of HAPI.

Sources

Follow CASRAI

Research-administration guidance, standards updates and independent tool reviews.

Ask CASRAI · free to try

Ask about HL7 Parser Guide: How to Read and Parse HL7 v2 Messages, MLLP Framing, ACK/NAK Codes, and Open-Source Libraries (Python, Java HAPI & .NET NHapi)

Ask your first 2 questions free below. Subscribers get 150 a day for $29 a month.

An AI assistant specialized in research administration. It cites the sources behind every answer, labels web answers and says when it can't answer.

Answers draw on CASRAI's guides and dictionary plus the federal and funder documents we index: Federal Register, Grants.gov, Regulations.gov and UKRI.

Works on this site and inside Claude, Cursor and the AI tools you already use.

Everything CASRAI publishes — this page, the dictionary, the guides and the news — stays free to read, with no account and no card.

Referenced across the research world

University of Cambridge logoColumbia University logoCrossref logoUniversity of Edinburgh logoHarvard University logoUniversity of Oxford logoPrinceton University logoStanford School of Medicine logoUniversity College London logoORCID logoUniversity of Cambridge logoColumbia University logoCrossref logoUniversity of Edinburgh logoHarvard University logoUniversity of Oxford logoPrinceton University logoStanford School of Medicine logoUniversity College London logoORCID logo
  • University of Cambridge logo
  • Columbia University logo
  • Crossref logo
  • University of Edinburgh logo
  • Harvard University logo
  • University of Oxford logo
  • Princeton University logo
  • Stanford School of Medicine logo
  • University College London logo
  • ORCID logo

View CASRAI adoption →