MLLP (Minimal Lower Layer Protocol) is the industry standard for transmitting HL7 v2 messages over TCP/IP. It frames each HL7 message with byte-level delimiters and uses persistent TCP connections to enable bidirectional, synchronous message exchange with receiver acknowledgments.
What is MLLP
MLLP is a lightweight, connection-oriented transport protocol designed specifically for HL7 v2 messaging. It wraps each HL7 message with start and end block characters, allowing a TCP stream to carry multiple messages and responses without ambiguity. MLLP does not add encryption, compression, or authentication by itself — those are layered above it (TLS, IPsec) or managed by the application.
The protocol exists in two major releases:
- Release 1: The original specification, widely deployed for HL7 v2 message transport across healthcare systems.
- Release 2: Extends Release 1 with commit-acknowledgement capabilities for message reliability. Required for HL7 Version 3 content transport, though marked as withdrawn from normative publication as of 2025.
MLLP is also known as the "Lower Level Protocol" or "LLP" in some vendor documentation.
How it works
MLLP is built on persistent TCP connections. A sender opens a single TCP connection to a receiver's MLLP port (typically port 2575 or 6660, depending on configuration) and keeps the connection open for the duration of the session. Over that connection, the sender transmits one or more HL7 messages, each framed with start and end block bytes.
Message Frame Structure
Each message is wrapped as follows:
[SB] <HL7 message content> [EB][CR]
Where:
[SB]= Start Block =0x0B(vertical tab, ASCII 11)[EB]= End Block =0x1C(file separator, ASCII 28)[CR]= Carriage Return =0x0D(ASCII 13)<HL7 message content>= the raw HL7 message (starting with MSH, no embedded framing bytes)
Wire-level Sequence
Sender Receiver
| |
+--- TCP SYN (port 2575) ------------------>|
|<------- TCP SYN-ACK ----------------------+
+--- TCP ACK -------------------------------->|
| |
+--- [0x0B] ADT^A01 payload [0x1C][0x0D] -->|
| |
|<------- [0x0B] MSA|AA|... [0x1C][0x0D] --+
| |
+--- [0x0B] ADT^A02 payload [0x1C][0x0D] -->|
| |
|<------- [0x0B] MSA|AA|... [0x1C][0x0D] --+
| |
+--- TCP FIN -------------------------------->|
|<------- TCP FIN-ACK ----------------------+
The sender transmits one complete message frame per interaction and waits for the receiver to return an acknowledgment (ACK) message, also wrapped in the same frame structure. The connection remains open until either side initiates TCP FIN (graceful close) or an error occurs.
When to use it
MLLP is the default choice for real-time, synchronous HL7 v2 message exchange within healthcare networks:
- ADT (Admission, Discharge, Transfer) messages — synchronizing patient registration across systems in near real-time.
- ORM (Order) messages — transmitting lab, radiology, or pharmacy orders from an EHR to ancillary systems with immediate confirmation.
- ORU (Result) messages — returning completed lab or imaging results back to the EHR.
- Legacy HL7 v2 integrations — most existing healthcare interfaces still rely on MLLP because it is simple, proven, and widely supported.
- Point-to-point messaging — when only one sender and one receiver need to exchange messages.
Do NOT use MLLP if you need:
- Publish-subscribe messaging (use SFTP batch files or a message broker instead)
- Fire-and-forget, unreliable delivery (MLLP always waits for an ACK)
- HTTP-based integrations with web services (use HL7 over HTTP instead)
- End-to-end encryption, authentication, or audit trails (layer TLS above MLLP; implement application-level signing if needed)
Configuration parameters
Port
MLLP listeners typically bind to TCP port 2575 by convention, though any port can be used. Some implementations default to 6660. Always verify the receiving system's documented MLLP listener port.
Encoding
MLLP supports single-byte and multi-byte character encodings, subject to framing-byte conflicts:
- Supported: ISO-8859-1 (Latin-1), ISO-8859-x, Windows CP1252, UTF-8, Shift_JIS
- Not recommended: UTF-16, UTF-32 (their multi-byte sequences may accidentally match framing bytes
0x0B,0x1C,0x0D)
Configure the character encoding at the interface level; it is not negotiated in the protocol itself.
Timeout and Keep-Alive
MLLP does not define timeouts or keep-alive parameters in the standard. Implementations should:
- Set TCP socket timeout (e.g., 30–60 seconds) to detect dead connections.
- Implement keep-alive (TCP_KEEPALIVE) to prevent firewalls from tearing down idle connections.
- Log and alert on any timeout-induced disconnections.
Message Size
MLLP does not enforce a maximum message size; it is stream-based. However:
- Most implementations limit individual HL7 messages to 10 MB to 50 MB to avoid memory exhaustion.
- Some systems configure a "max message length" parameter (e.g., Mirth Connect has
MAXIMUM_MESSAGE_SIZE). - Oversized messages should be rejected with an error ACK (MSA|AE) rather than silently truncated.
Security
MLLP itself provides no encryption, authentication, or integrity checking. All security must be implemented at lower or higher layers:
TLS/SSL Encryption
Wrap the MLLP connection in TLS 1.2 or later to encrypt messages in transit. This is the standard way to protect HL7 messages carrying PHI (Protected Health Information).
- Negotiate TLS before sending any MLLP frame.
- Use certificate-based mutual authentication (mTLS) to verify both sender and receiver identity.
- Do not use outdated protocols (SSL 3.0, TLS 1.0, TLS 1.1).
Authentication
MLLP does not define authentication. Common approaches:
- mTLS (mutual TLS): Each system presents a client certificate signed by a trusted CA.
- API key in MSH-3 (Sending Application): Encode an API key or token as part of the HL7 message itself (application-level, not protocol-level).
- Network-level ACLs: Restrict MLLP listener access to known sender IP addresses via firewall rules.
Data Integrity
MLLP does not include a checksum or MAC. Integrity is implicitly verified by TCP/IP (checksum), but not by the protocol:
- For additional protection, implement HMAC or digital signatures in the HL7 message payload (custom segment or MSH fields).
- Log all messages sent and received for audit and dispute resolution.
No Repudiation
MLLP is not non-repudiation-capable; either party can deny sending or receiving a message. To enforce audit trails:
- Store every sent/received message and its timestamp in a database.
- Include a digital signature (e.g., DSA, RSA) in the message if non-repudiation is a contractual requirement.
Pros & cons
Advantages
- Simple and proven: MLLP has been the HL7 v2 standard for three decades; virtually all healthcare systems understand it.
- Synchronous, reliable: The sender waits for an acknowledgment before considering the message delivered. No silent failures.
- Low overhead: Minimal framing (4 bytes per message), no compression or encryption overhead unless added.
- Persistent connections: A single TCP connection carries multiple messages, reducing connection setup overhead.
- Deterministic: No message loss, duplication, or reordering within a session (TCP guarantees in-order delivery).
- Real-time: Acknowledgments arrive immediately; no batching delays.
Disadvantages
- No broadcast or publish-subscribe: MLLP is strictly point-to-point; one sender, one receiver per connection.
- Blocking sender: The sender must wait for an ACK before sending the next message (synchronous only).
- No encryption by default: TLS must be added separately; bare MLLP sends PHI in cleartext.
- Firewall-unfriendly: Requires opening inbound TCP ports and maintaining persistent connections; some corporate firewalls restrict this.
- No built-in load balancing: Difficult to distribute load across multiple receiver instances without a proxy or message broker.
- Legacy protocol: New systems often prefer REST/HTTP or async message brokers; MLLP is declining in new integrations (though still dominant in legacy healthcare).
- Hard to debug: Binary framing and binary ACKs are opaque in logs; requires specialized tools to inspect frames.
Examples
Wire-level capture and sample handshake
Here is a minimal MLLP exchange captured at the byte level (shown in hex):
Sender TCP data (bytes):
0B 4D 53 48 7C 5E 7E 5C 26 7C 48 52 50 4F 52 54 41 4C 7C ... [HL7 message] ... 1C 0D
Breakdown:
0B = Start Block (SB) — marks message start
4D 53 48 ... = "MSH|^~&|HRPORTAL|..." (HL7 message payload in ASCII)
1C = End Block (EB) — marks message end
0D = Carriage Return (CR)
Receiver TCP data (ACK response, bytes):
0B 4D 53 41 7C 41 41 7C 4D 53 47 30 30 31 7C 7C 41 7C 32 2E 35 2E 31 1C 0D
Breakdown:
0B = Start Block
4D 53 41 ... = "MSA|AA|MSG001||A|2.5.1" (HL7 ACK message in ASCII)
1C = End Block
0D = Carriage Return
Annotated frame
A full HL7 ADT^A01 message wrapped in MLLP framing (shown in readable form with line breaks for clarity; the actual bytes flow continuously):
[SB]
MSH|^~&|HRPORTAL|VORRO|REGISTRY|STATE|20260701120000||ADT^A01^ADT_A01|MSG00001|P|2.5.1
EVN|A01|20260701120000|||^Admit
PID|||MRN123^^^VORRO||Okafor^Adaeze||19840712|F|||123 Main St^^Chicago^IL^60601^USA
PV1||I|CARDIO^04^01|H|||MD001^^^VORRO|||||||||CARD|||||20260701120000
[EB][CR]
Legend:
[SB] = 0x0B (vertical tab)
[EB] = 0x1C (file separator)
[CR] = 0x0D (carriage return)
The receiving system parses the frame, validates the message, and returns:
[SB]
MSA|AA|MSG00001||A|2.5.1
[EB][CR]
Where:
MSA-1 = AA (Application Accept — message was valid and accepted)
MSA-2 = MSG00001 (echo of the incoming message control ID)
MSA-3 (omitted) = no error text
MSA-4 (omitted) = no error code
MSA-5 = A (Expected sequence — normal)
MSA-6 = 2.5.1 (HL7 version)
Sample configuration (NextGen Connect / Mirth)
Here is an excerpt of an MLLP Sender connector configuration in NextGen Connect (formerly Mirth Connect):
<connector version="3.12.0" type="sender" transport="LLP">
<name>ADT to Registry MLLP Sender</name>
<properties>
<property name="host">registry.hospital.local</property>
<property name="port">2575</property>
<property name="processBatchAsResponse">true</property>
<property name="responseTimeout">30000</property>
<property name="reconnectMilliseconds">10000</property>
<property name="maxConnections">1</property>
<property name="charsetEncoding">UTF-8</property>
<property name="useSSL">true</property>
<property name="protocol">TLSv1.2</property>
<property name="keyStore">file:///etc/mirth/keystore.jks</property>
<property name="trustStore">file:///etc/mirth/truststore.jks</property>
</properties>
</connector>
And a receiver configuration (MLLP Listener):
<connector version="3.12.0" type="listener" transport="LLP">
<name>Inbound ADT MLLP Listener</name>
<properties>
<property name="port">2575</property>
<property name="timeout">60000</property>
<property name="useSSL">true</property>
<property name="protocol">TLSv1.2</property>
<property name="keyStore">file:///etc/mirth/keystore.jks</property>
</properties>
</connector>
Failure-mode example with error log
Scenario: Sender transmits an ADT^A01 with invalid data (e.g., missing required PID-5 field). Receiver rejects it with an error ACK.
Sender logs:
[2026-07-01 12:00:45.123] MLLP: Connected to registry.hospital.local:2575
[2026-07-01 12:00:45.456] MLLP: >> [MSG00042] ADT^A01 (1024 bytes)
[2026-07-01 12:00:45.789] MLLP: << MSA|AE|MSG00042|207^Missing required field PID-5|E|2.5.1
[2026-07-01 12:00:45.790] ERROR: Receiver rejected message MSG00042: 207 - Missing required field PID-5
[2026-07-01 12:00:45.800] ALERT: Requeue message for retry (attempt 1/3)
Receiver logs:
[2026-07-01 12:00:45.567] MLLP: Accepted connection from 192.168.1.100:54321
[2026-07-01 12:00:45.678] MLLP: << Received frame (1024 bytes)
[2026-07-01 12:00:45.700] INFO: Parsing ADT^A01 message
[2026-07-01 12:00:45.750] VALIDATE: PID-5 (patient name) is required but missing
[2026-07-01 12:00:45.780] >> Sending error ACK: MSA|AE|MSG00042|207^Missing required field PID-5|E|2.5.1
[2026-07-01 12:00:45.810] AUDIT: Message MSG00042 rejected; no database updates performed
Retry scenario: Sender retransmits with corrected data:
[2026-07-01 12:01:00.500] MLLP: >> [MSG00043] ADT^A01 (1100 bytes) [Retry of MSG00042]
[2026-07-01 12:01:00.823] MLLP: << MSA|AA|MSG00043||A|2.5.1
[2026-07-01 12:01:00.825] INFO: Message MSG00043 accepted successfully
Vorro support for MLLP
Vorro provides native MLLP support for healthcare integrations:
- MLLP Listener: Inbound MLLP receiver that accepts HL7 v2 messages, validates them, and generates immediate ACKs (AA = Application Accept, AE = Application Error, AR = Application Reject).
- MLLP Sender: Outbound MLLP client that transmits HL7 v2 messages to external systems and waits for ACK/NAK responses before marking messages as delivered.
- TLS/mTLS Encryption: All MLLP connections support optional TLS 1.2+ encryption and mutual certificate authentication.
- Connection Pooling: Vorro maintains persistent MLLP connections and reuses them across multiple messages to minimize latency.
- Message Validation: Vorro validates HL7 messages before transmission and generates appropriate error ACKs for malformed or incomplete incoming messages.
- Retry and Error Handling: Configurable retry logic with exponential backoff for failed transmissions; detailed logs of all MLLP handshakes and frame exchanges.
- Multi-version Support: Vorro parses and transmits HL7 v2.3, v2.4, v2.5.x, and v2.8.x messages over MLLP without modification.
Related pages
- Raw TCP/IP Transport
- SFTP Batch File Transport
- ADT Message: Admission, Discharge, Transfer
- ORM Message: Order
- MSA Segment: Message Acknowledgment
- MSH Segment: Message Segment Header
