HL7 v2 encoding guide

HL7 v2 Delimiters and Escape Sequences Explained: MSH-1, MSH-2 and Parsing Failures

An HL7 v2 message can look readable while a receiver assigns values to the wrong fields or components. The usual cause is structural: the parser assumed default separators, counted MSH like an ordinary segment, or treated an unescaped delimiter as data. This guide explains how MSH-1 and MSH-2 define the message, how the delimiter hierarchy works, and how to distinguish a real separator from an escaped literal character.

By Health Data ToolsPublished 28 July 2026Technically reviewed 28 July 2026Approx. 12-minute read
Use the declared HL7 version and interface profile. The familiar |^~\& characters are recommended defaults, not a reason to hard-code every parser. Version-specific rules and the receiving system's implementation guide still determine which values, repetitions and escape forms are accepted.

Overview

HL7 v2 separators carry structure, not decoration

In the commonly used delimited encoding, an HL7 v2 message is a hierarchy of segments, fields, repetitions, components and subcomponents. A separator character tells the parser where one structural value ends and the next begins. If the same character is intended as literal data, the sender must encode it with the matching HL7 escape sequence.

MSH|^~\&|SENDER|MAIN|RECEIVER|MAIN|20260728100000||ADT^A01^ADT_A01|MSG000301|P|2.5.1
PID|1||MRN100^^^MAIN^MR~NAT200^^^NATIONAL^NI||SAMPLE^ALEX

In this example, the vertical bar separates fields, the caret separates components, and the tilde separates two repetitions of PID-3. Those meanings come from the start of the MSH segment; they are not inferred from the PID segment.

Read MSH before parsing the rest of the message. The first separator after MSH defines MSH-1, and the following characters define the remaining encoding characters in MSH-2.

Delimiter hierarchy

What each delimiter separates

LevelCommon characterDeclared byPurpose
SegmentCR (0x0D)HL7 encoding rulesEnds one segment and begins the next segment after its three-character ID.
Field|MSH-1Separates numbered fields such as PID-3 and PID-5.
Component^First character of MSH-2Separates components inside a composite value.
Repetition~Second character of MSH-2Separates repeated occurrences of a repeatable field.
Escape\Third character of MSH-2Starts and ends an HL7 escape sequence.
Subcomponent&Fourth character of MSH-2Separates the smallest normally delimited parts of a composite value.
Truncation#Fifth character of current MSH-2Marks a value that was truncated when the applicable version and profile support it.

HL7 v2.5.1 defines four encoding characters in MSH-2: component, repetition, escape and subcomponent, with recommended value ^~\&.[1] The current HL7 v2.9 reference defines a fifth truncation character and recommends ^~\&#.[2]

This difference is version-sensitive. Do not reject a valid v2.5.1 message because it has four encoding characters, and do not assume the fifth character has truncation semantics without checking MSH-12 and the applicable interface profile.

MLLP control bytes are outside this hierarchy

VT, FS and the trailing CR used by Minimal Lower Layer Protocol (MLLP) frame the complete message at the transport layer. They are not HL7 field or component delimiters. See MLLP Framing Explained when the failure concerns bytes outside the HL7 payload.

MSH parsing

MSH-1 and MSH-2 make the header a special case

Most segments can be separated into fields by splitting on the declared field separator and numbering the resulting values after the segment ID. MSH cannot be handled that way without an adjustment because its separator characters define the parsing rules themselves.

MSH|^~\&|SENDER|MAIN|RECEIVER|MAIN|...

The positions are:

CharactersHL7 locationMeaning
MSHSegment IDIdentifies the message header segment.
|MSH-1The field separator used throughout this message.
^~\&MSH-2The component, repetition, escape and subcomponent characters for this v2.5.1 example.
SENDERMSH-3Sending application.
MAINMSH-4Sending facility.

A generic string split produces MSH as the first token and ^~\& as the second. If code labels the second token MSH-1, every later MSH field is shifted by one. A parser should extract MSH-1 from the character immediately after the segment ID, then read MSH-2 using that field separator.

Blank fields still preserve position

Two adjacent field separators represent an unpopulated position. They do not remove the field from the segment. In the common header fragment ...|20260728100000||ADT^A01..., the empty value between the two separators is still MSH-8; the following composite remains MSH-9.

Custom delimiters

A parser should follow MSH instead of assuming |^~\&

The standard recommends the familiar characters, but MSH can declare other permitted display characters. The following synthetic v2.5.1 header uses * as the field separator, $ for components, % for repetitions, ! for escapes and @ for subcomponents:

MSH*$%!@*SENDER*MAIN*RECEIVER*MAIN*20260728101500**ADT$A01$ADT_A01*MSG000302*P*2.5.1

A parser that splits this message on | will treat the entire MSH segment as one value. A parser that recognises the field separator but still splits MSH-9 on ^ will fail to identify ADT, A01 and ADT_A01.

Custom separators need exact agreement between systems. A receiver may constrain the base standard through its implementation profile and accept only the conventional characters. That is a profile or product rule; it does not justify ignoring the delimiters in a message that the interface otherwise claims to support.

Each delimiter must remain distinct

The field, component, repetition, escape and subcomponent functions cannot be distinguished if the same character is assigned to more than one role. Validate the MSH declarations before parsing clinical or administrative fields, and reject an ambiguous header with a specific diagnostic rather than continuing with shifted data.

Advertisement

Escape sequences

Encode a literal delimiter instead of changing the field structure

When a delimiter character is part of the value rather than the message structure, the sender must use the corresponding HL7 escape sequence. The current HL7 control rules state that delimiters included in a component must be escaped.[3]

Default formDecoded characterPurpose
\F\Field separatorRepresents a literal | when the default field separator is used.
\S\Component separatorRepresents a literal ^.
\R\Repetition separatorRepresents a literal ~.
\E\Escape characterRepresents a literal backslash when \ is the declared escape character.
\T\Subcomponent separatorRepresents a literal &.
\P\Truncation characterRepresents a literal truncation character in versions that declare it.

The backslash in these examples is not fixed. It represents whichever escape character is declared in MSH-2. In the custom header above, ! is the escape character, so a literal field separator * is encoded as !F!, not \F\.

Other standard and local escape forms

HL7 also defines forms such as \H\ and \N\ for highlighting, \Xdddd...\ for hexadecimal data, and \Zdddd...\ for locally defined escapes. Escape sequences cannot be nested.[3] A local \Z...\ value is interoperable only when both systems have agreed what its data means.

Do not treat a programming-language escape such as \n, \r or \x7C as an HL7 escape merely because it contains a backslash. Application source code may use those notations while constructing a string, but the transmitted HL7 payload must follow the declared HL7 encoding rules.

Worked examples

How one unescaped character changes the parsed message

Literal field separator in a comment

The intended NTE-3 value is “Allergy noted: fish | shellfish”. Sending the literal vertical bar changes the segment structure:

Incorrect
NTE|1|L|Allergy noted: fish | shellfish

Parsed result
NTE-3 = Allergy noted: fish 
NTE-4 =  shellfish

With the default escape character, the correct encoding is:

NTE|1|L|Allergy noted: fish \F\ shellfish

The receiver parses the segment boundaries first and then decodes \F\ as a literal | inside NTE-3.

Literal repetition separator in free text

Incorrect
OBX|1|TX|COMMENT||Expected range 5~10

Correct
OBX|1|TX|COMMENT||Expected range 5\R\10

If OBX-5 is repeatable in the applicable message definition, the unescaped tilde creates two repetitions. Even where the receiving profile does not permit repetition, the parser still sees a structural delimiter and may reject or misrepresent the value.

Repeating identifiers are not escaped data

PID|1||MRN100^^^MAIN^MR~NAT200^^^NATIONAL^NI||SAMPLE^ALEX

The tilde is structural here. It separates two legitimate repetitions of PID-3. Escaping it as \R\ would collapse the content into one repetition containing a literal tilde, which changes the identifier model.

Processing order

Parse structure before decoding escaped delimiter values

A practical receiver sequence is:

  1. Remove or separately process transport framing such as MLLP.
  2. Identify the MSH segment and extract the field separator from the character after MSH.
  3. Read MSH-2 and validate its encoding characters against the declared version and profile.
  4. Split the message into CR-terminated segments.
  5. Parse fields, repetitions, components and subcomponents using the declared characters.
  6. Decode escape sequences within the resulting data values according to their context.
  7. Apply message-profile, field-usage, data-type and terminology validation.

Decoding \F\ into | before field parsing can turn literal data into a structural separator. Conversely, never de-escaping means the application receives encoded text rather than the intended value. Preserve the original payload alongside the parsed representation so the transformation can be checked.

Common failures

Delimiter defects and their observable effects

SymptomLikely causeWhat to verify
Every MSH field is off by oneThe parser treated the first split token as MSH-1 and ignored the special position of the field separator.How MSH-1, MSH-2 and MSH-3 are extracted.
The whole message appears as one segmentSegments use LF or visible text instead of the expected CR, or transport/log processing changed line endings.Raw bytes between segment IDs.
A name, comment or address creates extra fieldsA literal field separator was not encoded with the field escape.Original value before encoding and the transmitted field.
One identifier becomes several repetitionsA literal repetition character was sent unescaped.The declared repetition separator and field repeatability.
Message type or trigger event is blankThe parser hard-coded ^ although MSH-2 declared another component separator.MSH-2 and the literal MSH-9 value.
Escapes remain visible to the applicationThe parser preserved structure but did not de-escape the final data values.Processing stage at which escape decoding occurs.
Escaped text creates extra fieldsThe implementation decoded escapes before structural parsing.Parse and de-escape order.
Message works in one engine but fails in anotherOne product tolerates undeclared defaults, malformed MSH-2 or non-standard line endings.Raw message, declared version and both products' profiles.
A v2.5.1 message is rejected for a missing fifth encoding characterCurrent v2.9 MSH-2 rules were applied without considering MSH-12.Version-specific MSH-2 length and profile constraints.

Troubleshooting

A repeatable delimiter investigation

  1. Capture the exact transmitted payload. Use the bytes or unmodified message before a log viewer changes control characters or line endings.
  2. Separate transport from content. Remove MLLP boundaries without discarding the CR bytes that terminate HL7 segments.
  3. Inspect the first MSH characters. Record MSH-1, the complete MSH-2 value and MSH-12.
  4. Check delimiter uniqueness. Confirm that no structural role reuses the field separator or another encoding character.
  5. Parse one failing segment manually. Mark fields first, then repetitions, components and subcomponents.
  6. Locate literal delimiter data. Compare the source application value with its transmitted HL7 encoding.
  7. Check escape boundaries. Each escape must use the declared escape character at both ends and must not contain another nested escape.
  8. Compare before and after de-escaping. Verify that structural positions stay unchanged while the final data value gains the intended literal character.
  9. Apply the interface profile. Confirm field repeatability, data type, supported escape forms and any delimiter constraints.
  10. Test the corrected payload independently. Use the same raw message on both sides before changing mapping or business logic.

The HL7 Message Formatter & Viewer reads common delimiters from MSH and exposes fields, repetitions, components and subcomponents. Use it with synthetic messages to confirm the structure before investigating downstream mappings.

Checklist

What to define in an HL7 v2 interface

  • The supported HL7 version and message profile.
  • Whether only the recommended delimiters are accepted or valid custom delimiters are supported.
  • The expected segment separator and how copied or file-based messages are normalised.
  • How MSH is parsed without shifting its field numbers.
  • Which fields may repeat and how repetitions are represented in the destination model.
  • Which standard escape sequences and local \Z...\ forms are supported.
  • Whether escape decoding occurs in the integration engine, receiving application or both.
  • How invalid, incomplete or nested escape sequences are reported.
  • How the original payload and parsed field locations are logged without retaining patient data.
  • Which test cases cover literal field, component, repetition, escape and subcomponent characters.

FAQ

Frequently asked delimiter questions

Is the field separator always a vertical bar?

No. | is the recommended and overwhelmingly familiar value, but MSH-1 defines the field separator for the message. A receiving profile can constrain which characters it accepts.

Why is the vertical bar after MSH called MSH-1?

It is both the separator after the segment ID and the value of MSH-1. The following token contains MSH-2, so MSH needs special handling when fields are numbered.

Should an ampersand in free text be sent as \T\?

Yes when & is the declared subcomponent separator and the character is intended as literal data in a location where it would otherwise be interpreted structurally. Use the escape character declared by that message.

Can I replace every backslash escape before splitting fields?

No. Decoding a field escape before structural parsing can introduce a literal field separator that is then mistaken for structure. Parse using the declared separators first, then decode escapes inside the resulting values.

Does MSH-2 always have five characters?

No. HL7 v2.5.1 defines four encoding characters. The current v2.9 reference includes a fifth truncation character. Interpret the header according to MSH-12 and the applicable interface profile.

Advertisement

Primary references

HL7 Version 2 encoding sources

  1. HL7 Version 2.5.1, Chapter 2: Control: MSH-1, the four MSH-2 encoding characters, recommended values and escape-sequence rules for a widely deployed HL7 v2 version.
  2. HL7 Version 2+: MSH Message Header Segment: current MSH-1 and five-character MSH-2 definitions, including the truncation character.
  3. HL7 Version 2+: Control, Use of Escape Sequences in Fields: delimiter escapes, formatting and hexadecimal forms, local escapes, and the prohibition on nested escape sequences.
  4. HL7 Version 2.9: Introduction and Encoding Rules: field-delimited message structure, segment separation, character-set context and positional field processing.

Local implementation guides and interface agreements remain authoritative for supported versions, delimiter constraints, field repetitions, escape handling and receiving-system behaviour.