Guide
How to test an HL7 v2 interface
An HL7 v2 interface is a dialect that two applications negotiated, so the object under test is the pair of endpoints and their two profiles. Work runs in seven passes: inventory the interactions, capture what the wire carries, constrain every optional element into a profile, build the message corpus, exercise both acknowledgement modes, test the receiving application, then re-run on each version change.
- Written for
- For a CTO
- Last revised
- 10 September 2026
What is under test when the interface is HL7 v2?
Two applications and the agreement between them. HL7 publishes v2 as a framework: each event comes with a starting template, and the template exists to be narrowed for one use in one context. NIST, which builds the conformance tooling, describes those templates as made up "mostly of optional data elements". Aim a test plan at the standard itself and there is almost nothing to assert, because a standard that permits nearly everything cannot fail anything.
A live interface runs on local decisions instead. Each message announces the version its sender speaks in MSH-12, and the receiver matches that value against its own before it trusts its reading of the rest. The delimiter set is announced in the message too, and the Control chapter lets a site limit which of the possible delimiters are in use by "negotiations between applications". Local segments are permitted outright, since every segment ID beginning with Z belongs to locally defined segments. Which optional fields the receiving application actually insists on was decided at the receiving site, and HL7 states the mechanism plainly. A profile is allowed to "reduce or eliminate the optionality" that the template left open, which means an element optional in HL7 v2 can be mandatory in the dialect.
That is the dialect, and it is the object under test. One message can satisfy HL7 v2 and be refused at the far end on the same afternoon, and both results are correct. The conformance requirements HL7 v2 places on a message set the floor. The dialect sets the pass mark.
There are two dialects. A message profile documents the interface capability of a single application; HL7 notes that the expectations of the two applications "may, and often do, differ", and that the requirements of sender and receiver have to be compatible for the interface to work at all. Where both ends genuinely hold the same document, the profile declares its role as "both". Where they do not, the delta between the two documents is a defect list that can be produced before a single message is sent.
Which pass comes first, and what does each one hand over?
Seven passes, each ending in an artefact somebody outside the test team can read. The order is load-bearing: pass 3 has no input without pass 2, and passes 5 to 7 are the ones a project usually finds it has skipped.
The seven passes
- Inventory the interactions. Name each pair of applications and each direction.
- Capture the wire. Compare real traffic against the written specification.
- Constrain the profile. Remove every optional and undeclared conditional element.
- Build the corpus. Create one message set per trigger event.
- Exercise the reply. Test both acknowledgement modes and every reply code.
- Test the receiver. Check what the receiving application does with the data.
- Re-run on change. Repeat after each version, code set and site agreement change.
Pass 1. Which interactions cross the boundary?
Begin with pairs of applications, because that is the unit HL7 counts in. Its conformance methodology calls the conversation between two actors an interaction, binds each interaction to exactly one message profile, and calls a roundtrip of them a transaction. An interface engine serving four downstream systems is four inventories.
Each row of the inventory names the sending application, the receiving application, the message type, the trigger event, the message structure and the direction. Those three identifiers live in three separate HL7 tables and travel together in MSH-9: the type from Table 0076, the trigger event from Table 0003, the structure from Table 0354. An inventory that records "the ADT feed" has already thrown away the distinction the receiver routes on.
The catalogues are far larger than any real integration. There are 159 message type codes in Table 0076. There are 384 trigger events in Table 0003. Against that, the message maps HL7 publishes for its own V2-to-FHIR work come to thirteen messages in five chapters, and those five describe most of what a health software team meets: patient administration, orders, observation reporting, medical records and scheduling, with financial transactions in chapter 6 alongside them.
Produces: an interaction inventory, one row per profile, paired into transactions.
Pass 2. What does the wire carry that the specification never mentions?
Capture before you read. The specification describes an interface somebody intended. The capture describes the one that runs. Four readings come out of the same sample and none of them can be taken from a document.
The first is the version, read from MSH-12 at each endpoint separately, never from the project's belief about which release the products support. The second is the delimiter set in use, read from MSH-1 and MSH-2. Only the segment terminator is fixed. It is a carriage return, and the Control chapter puts it beyond the reach of implementers. The other six are declared inside each message, so a parser written against the recommended values alone carries an untested branch.
The third reading is the local extension inventory: which Z-segments appear, which HL7 segments have local fields hung off the end, and which of those the downstream application depends on. Z messages and Z segments are permitted outright. Local fields added to an HL7 segment are a different case, and the standard calls the practice "ill-advised", expecting it to cause conformance trouble at the next release. The fourth reading is MSH-21, the Message Profile Identifier. A sender that populates it is asserting which profile its traffic claims to satisfy; in most captured traffic the field is empty, so the traffic claims nothing.
A capture taken from an ADT feed carries patient demographics and visit data, which makes it PHI from the moment it is stored. We do not need production PHI to test. Environments run on synthetic and de-identified data. What a client can ask about that arrangement, and what the answers are, sits on how we work with protected health information. In practice the capture is reduced to structure, codes and cardinalities, and the message set that stands in for it is assembled the way testing with synthetic PHI data describes.
Produces: a wire report giving version per endpoint, delimiter set, Z elements, local fields and profile claims, each set against the specification.
Pass 3. How do you make the dialect able to fail a message?
Write the dialect down as a profile, then constrain it. An implementation profile may not leave an element at optional usage or at undeclared conditional usage: each one has to become R, RE, X or a declared C(a/b). Every conditional element needs its predicate spelled out, because the declared form fixes the usage for the true outcome and the false outcome separately.
Constraints move one way only. A required element cannot be loosened back to optional, so a profile that appears to widen the base standard is a defect in the document. Ten constraint types are available, and a review that inspects usage alone leaves nine of them unread: usage, cardinality, data type, vocabulary, length, content, conformance statement, co-constraint, slicing and semantic refinement.
Vocabulary is the constraint that ages fastest. A profile binding a diagnosis field to ICD-10-CM has bound it to a set CMS reissues every year, and the FY 2027 files carry their own scope: they cover encounters between 1 October 2026 and 30 September 2027. Serialisation is a second trap at the same field. The ICD-10-CM tabular file writes codes with a dot, A00.0, and the flat distribution files write the same code without one, A000. A validator that accepts one form while the receiver stores the other yields an interface that passes its test and loses data.
This pass is finished when its output is machine-readable. NIST's authoring tool exports the constrained definition as an XML profile, and validation is then a mechanical comparison of one message instance against the rules that profile carries. NIST also names the usual cause of interface arguments: implementation guides written in word processors, which produce requirements that are ambiguous and inconsistent between readers.
Produces: an XML conformance profile per interaction, and a written list of every element the receiving site requires while the base standard leaves it optional.
Pass 4. Which messages belong in the corpus?
One set per trigger event. Message structures are shared between events, and a corpus indexed by structure quietly drops the events hiding behind each one. Table 0354 records the sharing per code.
| Message structure | Trigger events using it |
|---|---|
| ADT_A01 | Four: A01, A04, A08, A13 |
| MDM_T02 | Five: T02, T04, T06, T08, T10 |
| SIU_S12 | Fourteen, running from S12 to S26 |
| ORU_R01 | One: R01 |
| DFT_P03 | One: P03 |
Send one ADT_A01 instance and you have covered a quarter of what that structure carries. A receiver that dispatches on the structure ID alone has discarded the trigger event before its own business logic sees the message, and the corpus is the only place that shows up.
Deprecated traffic belongs in the corpus too, on the evidence of pass 2 rather than on the state of the current standard. Chapter 4 withdrew ORM at v2.7, having kept it for backward compatibility from v2.4, and the O01 event is flagged deprecated in Table 0003. A map for ORM_O01 is published all the same, on HL7's stated reasoning that components of that vintage remain in use on older traffic. Where a capture contains ORM^O01, the profile has to describe ORM^O01.
Each message then needs its negative twin, because the rejection path is the half of the interface nobody has watched run. Build one invalid instance per rule the profile states: an R element missing, an X element present, a value outside the bound value set, a field longer than its declared maximum, an undocumented Z-segment. NIST covers invalid scenarios in its test case tool alongside valid ones, and it exposes validation through REST and web services with the validation JAR published as well, so the assertions can run inside the same build job.
Produces: a message corpus indexed by trigger event, with a valid and an invalid instance for each profile rule, replayable by the receiving site.
Pass 5. How is the reply tested?
Against the choreography, in both modes. HL7 v2 defines two acknowledgement modes, and the note attached to the older one records that enhanced mode replaced it in version 2.2 and that implementers are required to value MSH-15 and MSH-16 in every message. MSH-15 governs the immediate acknowledgement and MSH-16 the application acknowledgement. Each takes one of four values from Table 0155: AL always, NE never, ER only on errors and rejections, SU only on success. Four values across two fields is sixteen combinations per trigger event, and a suite that sends whichever combination the interface engine defaults to has tested one.
From version 2.9 every chapter is obliged to publish the acknowledgement choreography inside its trigger event definitions, which turns the expected reply into a documented value a tester can assert. Patient Administration shows the shape. An ADT^A01^ADT_A01 message carrying NE in MSH-15 gets no immediate acknowledgement at all, while AL, ER and SU each oblige the receiver to return an ACK^A01^ACK.
MSA-1 can carry six codes. AA, AE and AR report what the receiving application did with the message. CA, CE and CR report whether the receiver would accept the message for processing at all. CR is the one to provoke on purpose: a receiver returns it when the message type in MSH-9, the version in MSH-12 or the processing ID in MSH-11 is unacceptable to it. Sending an instance stamped with a version the far end does not accept is a single test that tells you whether version skew arrives as a rejection or as silence.
Two smaller checks sit in the same pass. The receiver returns the sender's MSH-10 Message Control ID inside MSA-2, which is how a sender pairs a reply with its message and recognises a duplicate; a receiver that mints a fresh identifier breaks reconciliation while breaking no rule the validator knows. And three MSA sequences now carry cardinality 0 to 0, MSA-3, MSA-5 and MSA-6, with error detail having moved to the ERR segment. An interface still parsing an error string out of MSA-3 is parsing a field that no longer exists.
Produces: an acknowledgement choreography table per trigger event, and a recorded result for every reply code the interface can return.
Pass 6. How do you test the application that receives the message?
End to end, because conformance stops at the boundary and the value does not. HL7 concedes the point: judging the receiving side is "a more difficult task", since it depends on what the receiver consumes and what it then does. Testing a receiver properly needs acknowledgement and functional requirements written per element, and HL7 observes that v2 interface specifications usually leave exactly that out of scope. Its own remedy is a second document, an edge functional requirement guide, written for a class of receiver.
NIST draws the same line from the tooling side. HL7 v2 is scoped to what is exchanged between applications and imposes almost nothing on how the data is processed afterwards, so data quality and business rules ride on top of conformance. The example NIST gives of such a rule is that a date of administration must fall after the date of birth, which no conformance run against the base standard will ever raise.
The assertion for this pass therefore lives on the far side of the interface. A value sent in a named field reaches the record a clinician reads, against the right patient, with the right status, and a repeat of the same event updates that record instead of creating a second one. Reading a message the whole way through to the stored record, and the environment needed to do it, is the subject of testing an EHR integration.
Produces: a receiver assertion for every element that carries meaning, each stated as an outcome observable in the receiving application.
Pass 7. What gets re-run when a version or a code set changes?
Less than everything, if the standard's own change rules are used to select. HL7 states two compatibility axioms: an old receiver should go on processing new messages without error, and a new receiver should understand old messages. Above them sits a warning that where the rules are silent, nothing should be assumed allowed.
The first axiom converts into a test case with no translation. A receiver is obliged to ignore any constituent it did not expect, and it must not raise an application failure merely because something extra arrived. It may tell the sender that it ignored the element. Send a message with an element the receiver has never seen, and assert that it processes the message and stays up.
The rules also say where change lands. A new field is appended after the last field of its segment, and a new component after the last component of its data type. Anything reading by position therefore meets each upgrade at one spot, immediately past the last element it knows about. Repeatability widens and never narrows: a non-repeating field may become repeating, and a repeating field may never go back. Optional fields may be tightened to conditional or required. Nothing is deleted, only deprecated, and two sites may agree between themselves to stop supporting a deprecated constituent. Agreements of that kind live outside the standard and outside the specification, so they reappear as production defects unless pass 2 recorded them.
Three events trigger a re-run: a version change at either endpoint, a code set release such as the annual 1 October ICD-10-CM update, and any amendment to a site agreement. Choosing the affected subset instead of repeating the whole suite is the general problem described in choosing a regression set in regulated software.
Produces: a change-triggered regression set, mapping each rule in the profile to the messages that exercise it.
What do you hold at the end?
Six documents, each answering a question somebody asks later.
| Artefact | The question it answers |
|---|---|
| Interaction inventory | Which conversations cross this boundary, in which direction, and which profile governs each |
| Wire report | What the running interface carries today that no specification describes |
| XML conformance profile | What a validator measures each message instance against |
| Usage difference list | Which elements the receiving site requires while the base standard leaves them optional |
| Validation reports | Which instances failed which rule, located by element path and line number |
| Acknowledgement choreography table | Which reply is due for each trigger event at each MSH-15 and MSH-16 value |
The usage difference list is the one that pays for the engagement, because it reports the defects in advance of their happening. Every line on it stands for a message the sending application would otherwise emit and the receiver would otherwise refuse.
Validation reports are also the artefact an external programme consumes. NIST releases its tooling as public domain and names the ONC Health IT certification, IHE Connect-a-thon events and AIRA assessment among the users of it, so a product heading for ONC certification testing requirements is generating evidence in a format that programme already reads. The stakes differ by setting: inside a certification programme a conformance violation can and should end in a failure, while the same violation on a live interface may produce anything from a refusal to no reaction at all.
What breaks at the boundary and nowhere else?
Every item below passes a unit test on either side and fails once the two systems are connected.
- The sender uses a delimiter the receiving parser does not expect, because the site narrowed the delimiter subset by agreement and neither specification recorded it.
- A patient name contains an ampersand. Delimiters inside a component have to be escaped, and a sender that skips the escape alters the meaning of the component instead of failing outright.
- The receiver dispatches on the message structure ID and never reads the trigger event, so A01, A04, A08 and A13 all land in one handler.
- Both applications rely on a field carried in a Z-segment that neither profile documents. Undocumented content is a violation against the profile, even though the receiver is free to process the message anyway.
- The receiver mints its own MSA-2 instead of returning the sender's MSH-10, so retransmissions are stored twice and neither log shows anything unusual.
- One endpoint is upgraded, new fields arrive after the last one a segment had, and the positional reader at the other end runs past what it understands.
- A segment group was defined with no required first segment. The anchor segment rule exists from version 2.5 onward to stop unparsable messages being defined by accident.
- A diagnosis code crosses as A000 and is stored where A00.0 was expected, so the message validates and the report built on it comes back empty.
- Neither MSH-15 nor MSH-16 carries a value, although both are required to, and each side has inferred the reply behaviour from what the other does at runtime.
- Both ends declare conformance while holding different profiles, which HL7 permits and which fails at the first element where the two documents disagree.
Where this runs as an engagement rather than an internal exercise, the scope, the artefacts and the handover are set out under an interoperability testing engagement. Where the interface is new and the choice of standard is still open, the trade-off is weighed in HL7 v2 against FHIR.
Every quotation and count above was read from a primary document on 2026-09-02. The documents themselves, their capture dates and the caveat that applies to the conformance text are listed at the foot of the standard page linked at the top of this one.
What do buyers ask about this?
- Can we start before the receiving vendor sends us a specification?
- Passes 1 and 2 need only your own traffic and your own code, and they produce the interaction inventory and the wire report. Pass 3 needs a document from the other side. HL7 records that the expectations of the two applications often differ and that the two sets of requirements have to be compatible, so until the second document arrives every finding is a question addressed to somebody else.
- How many test messages does one interface need?
- Count interactions first, then trigger events, then reply codes. HL7 binds one message profile to one interaction. Table 0354 shows four trigger events sharing the ADT_A01 structure and fourteen sharing SIU_S12. MSA-1 can return six codes. One instance per message structure therefore covers a small share of the behaviour sitting behind that structure.
- Does a validation tool remove the need for a written profile?
- No, because the profile is the tool's input. NIST's validators read the constraints out of an XML profile and assert them against each message instance, so a validator with no profile can report syntax and little else. The base standard leaves most elements optional, which means it permits almost any content a sender chooses to put on the wire.
- The interface has run in production for years. What is there to test?
- The parts nobody wrote down. A working dialect rests on local agreements about delimiters, Z-segments, deprecated messages and version behaviour, and HL7 lets two sites agree between themselves to drop support for deprecated constituents. The event that breaks such an agreement is usually an upgrade at one end, and by then the agreement exists only in the running code.
- Can we reuse the HL7 v2 test suite when an integration moves to FHIR?
- The corpus does not transfer, and the way you interrogate the far end changes. A FHIR server answers GET [base]/metadata with a CapabilityStatement, and FHIR conformance can only be claimed for what that statement describes. HL7 v2 has no comparable request. Its nearest equivalent is MSH-21, a claim carried inside a message that has already been sent.
Which standards does this touch?
Which product types does this apply to?
Which of our services test it?
What does validating your product actually involve?
Answer four questions about your markets, your product type and its integrations. You get the standards that reach you, the artefacts each one asks you to produce, and which of them a test supplier delivers.