Working with Z-Segments (Custom Segments)
Z-segments are custom segments that extend the HL7 standard to meet organization-specific needs. By convention, any segment starting with āZā is considered locally defined and will not conflict with official HL7 segments. Common uses include adding billing codes, custom patient attributes, or facility-specific workflow data. HAPI treats Z-segments as generic segments, so you access fields by index rather than through typed accessors.
import ca.uhn.hl7v2.DefaultHapiContext;
import ca.uhn.hl7v2.HapiContext;
import ca.uhn.hl7v2.model.Message;
import ca.uhn.hl7v2.model.Segment;
import ca.uhn.hl7v2.model.Type;
import ca.uhn.hl7v2.parser.Parser;
import ca.uhn.hl7v2.util.Terser;
public class ZSegmentExample {
public static void main(String[] args) {
// Message with custom Z-segment
String hl7Message =
"MSH|^~\\&|SENDING_APP|SENDING_FACILITY|RECEIVING_APP|RECEIVING_FACILITY|20231117120000||ADT^A01|MSG00001|P|2.5\r" +
"PID|1||123456^^^HOSPITAL^MR||DOE^JOHN^A||19800115|M\r" +
"ZPD|PREMIUM|VIP_PATIENT|PRIVATE_ROOM_PREFERRED\r"; // Custom Z-segment
try {
HapiContext context = new DefaultHapiContext();
Parser parser = context.getGenericParser();
Message message = parser.parse(hl7Message);
// Access Z-segment using Terser
Terser terser = new Terser(message);
System.out.println("Accessing Z-Segment (ZPD):");
// Get the ZPD segment
Segment zpd = (Segment) message.get("ZPD");
if (zpd != null) {
System.out.println("ZPD Segment found");
// Access fields in Z-segment
Type field1 = zpd.getField(1, 0);
Type field2 = zpd.getField(2, 0);
Type field3 = zpd.getField(3, 0);
System.out.println("ZPD-1 (Patient Category): " + field1.encode());
System.out.println("ZPD-2 (VIP Status): " + field2.encode());
System.out.println("ZPD-3 (Room Preference): " + field3.encode());
}
context.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Message Enrichment
Message enrichment adds data to an existing message before forwarding it to downstream systems. This is common in integration scenarios where the original message lacks information needed by the receiver. For example, an ADT message from registration might need next-of-kin information added from a separate system before being sent to the nursing unit. HAPI makes enrichment straightforward by allowing you to access and modify segments on a parsed message object.
import ca.uhn.hl7v2.DefaultHapiContext;
import ca.uhn.hl7v2.HapiContext;
import ca.uhn.hl7v2.model.v25.message.ADT_A01;
import ca.uhn.hl7v2.model.v25.segment.NK1;
import ca.uhn.hl7v2.parser.Parser;
public class MessageEnrichmentExample {
public static void main(String[] args) {
String basicMessage =
"MSH|^~\\&|SENDING_APP|SENDING_FACILITY|RECEIVING_APP|RECEIVING_FACILITY|20231117120000||ADT^A01|MSG00001|P|2.5\r" +
"PID|1||123456^^^HOSPITAL^MR||DOE^JOHN^A||19800115|M\r";
try {
HapiContext context = new DefaultHapiContext();
Parser parser = context.getPipeParser();
ADT_A01 adtMessage = (ADT_A01) parser.parse(basicMessage);
// Add next of kin information
NK1 nk1 = adtMessage.getNK1();
nk1.getSetIDNK1().setValue("1");
// Name of next of kin
nk1.getNKName(0).getFamilyName().getSurname().setValue("DOE");
nk1.getNKName(0).getGivenName().setValue("JANE");
// Relationship
nk1.getRelationship().getIdentifier().setValue("SPO");
nk1.getRelationship().getText().setValue("SPOUSE");
// Address
nk1.getAddress(0).getStreetAddress().getStreetOrMailingAddress()
.setValue("123 MAIN ST");
nk1.getAddress(0).getCity().setValue("CITY");
nk1.getAddress(0).getStateOrProvince().setValue("ST");
nk1.getAddress(0).getZipOrPostalCode().setValue("12345");
// Phone
nk1.getPhoneNumber(0).getTelephoneNumber().setValue("5555555556");
// Encode enriched message
String enrichedMessage = parser.encode(adtMessage);
System.out.println("Original Message:");
System.out.println(basicMessage);
System.out.println("\nEnriched Message:");
System.out.println(enrichedMessage);
context.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Message Transformation (Version Conversion)
Converting messages between HL7 versions is sometimes necessary when integrating systems that use different versions. HAPI does not provide automatic version conversion because field definitions and semantics can differ. Instead, use Terser to copy field values between version-specific message objects. The Terser path syntax works across versions, making it ideal for transformation scenarios. Be aware that some data may require mapping or reformatting when moving between versions.
import ca.uhn.hl7v2.DefaultHapiContext;
import ca.uhn.hl7v2.HapiContext;
import ca.uhn.hl7v2.model.Message;
import ca.uhn.hl7v2.parser.Parser;
import ca.uhn.hl7v2.util.Terser;
public class VersionConversionExample {
public static void main(String[] args) {
// V2.3 message
String v23Message =
"MSH|^~\\&|SENDING_APP|SENDING_FACILITY|RECEIVING_APP|RECEIVING_FACILITY|20231117120000||ADT^A01|MSG00001|P|2.3\r" +
"PID|1||123456^^^HOSPITAL^MR||DOE^JOHN^A||19800115|M\r";
try {
HapiContext context = new DefaultHapiContext();
Parser parser = context.getGenericParser();
// Parse V2.3 message
Message v23Msg = parser.parse(v23Message);
System.out.println("Original Version: " + v23Msg.getVersion());
// Create V2.5 message
ca.uhn.hl7v2.model.v25.message.ADT_A01 v25Msg =
new ca.uhn.hl7v2.model.v25.message.ADT_A01();
v25Msg.initQuickstart("ADT", "A01", "P");
// Copy data using Terser
Terser sourceTerser = new Terser(v23Msg);
Terser targetTerser = new Terser(v25Msg);
// Copy common fields
targetTerser.set("/.MSH-3-1", sourceTerser.get("/.MSH-3-1"));
targetTerser.set("/.MSH-4-1", sourceTerser.get("/.MSH-4-1"));
targetTerser.set("/.MSH-5-1", sourceTerser.get("/.MSH-5-1"));
targetTerser.set("/.MSH-6-1", sourceTerser.get("/.MSH-6-1"));
targetTerser.set("/.MSH-10", sourceTerser.get("/.MSH-10"));
targetTerser.set("/.PID-3-1", sourceTerser.get("/.PID-3-1"));
targetTerser.set("/.PID-5-1-1", sourceTerser.get("/.PID-5-1"));
targetTerser.set("/.PID-5-2", sourceTerser.get("/.PID-5-2"));
targetTerser.set("/.PID-7-1", sourceTerser.get("/.PID-7"));
targetTerser.set("/.PID-8", sourceTerser.get("/.PID-8"));
// Encode V2.5 message
String v25Message = parser.encode(v25Msg);
System.out.println("\nV2.3 Message:");
System.out.println(v23Message);
System.out.println("\nConverted to V2.5:");
System.out.println(v25Message);
context.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Batch Message Processing
Batch processing handles multiple messages efficiently, which is useful for file-based interfaces, bulk migrations, or processing message backlogs. When processing batches, track success and failure counts to report processing results. Java streams with parallel execution can significantly improve throughput for CPU-bound parsing operations. However, ensure thread safety when writing results to shared resources.
import ca.uhn.hl7v2.DefaultHapiContext;
import ca.uhn.hl7v2.HapiContext;
import ca.uhn.hl7v2.model.Message;
import ca.uhn.hl7v2.parser.Parser;
import java.util.ArrayList;
import java.util.List;
public class BatchProcessingExample {
public static void main(String[] args) {
try {
HapiContext context = new DefaultHapiContext();
Parser parser = context.getGenericParser();
// Simulate reading multiple messages
List<String> messages = new ArrayList<>();
messages.add(
"MSH|^~\\&|APP1|FAC1|APP2|FAC2|20231117120000||ADT^A01|MSG001|P|2.5\r" +
"PID|1||P001^^^HOSPITAL^MR||DOE^JOHN^A||19800115|M\r"
);
messages.add(
"MSH|^~\\&|APP1|FAC1|APP2|FAC2|20231117120100||ADT^A01|MSG002|P|2.5\r" +
"PID|1||P002^^^HOSPITAL^MR||SMITH^JANE^B||19850620|F\r"
);
messages.add(
"MSH|^~\\&|APP1|FAC1|APP2|FAC2|20231117120200||ADT^A01|MSG003|P|2.5\r" +
"PID|1||P003^^^HOSPITAL^MR||JONES^BOB^C||19700310|M\r"
);
System.out.println("Processing batch of " + messages.size() + " messages\n");
int successCount = 0;
int errorCount = 0;
for (String hl7String : messages) {
try {
Message message = parser.parse(hl7String);
ca.uhn.hl7v2.util.Terser terser =
new ca.uhn.hl7v2.util.Terser(message);
String msgControlId = terser.get("/.MSH-10");
String patientId = terser.get("/.PID-3-1");
String patientName = terser.get("/.PID-5-1-1") + ", " +
terser.get("/.PID-5-2");
System.out.println("Processed Message: " + msgControlId);
System.out.println(" Patient: " + patientName + " (ID: " + patientId + ")");
successCount++;
} catch (Exception e) {
System.err.println("Error processing message: " + e.getMessage());
errorCount++;
}
}
System.out.println("\nBatch Processing Summary:");
System.out.println("Total Messages: " + messages.size());
System.out.println("Successful: " + successCount);
System.out.println("Errors: " + errorCount);
context.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}