Working with Complex Data Types
FHIR defines various complex data types for representing healthcare concepts. Understanding these data types is essential because they appear throughout FHIR resources and provide semantic precision for clinical data. Complex types encapsulate related properties together, ensuring consistent representation across different implementations and enabling proper clinical interpretation.
CodeableConcept
Represents a coded value with multiple possible codings. CodeableConcept is perhaps the most important complex type in FHIR, allowing a single clinical concept to be expressed in multiple terminologies simultaneously. This supports interoperability between systems using different code systems while preserving the original coding and providing a human-readable text fallback.
import org.hl7.fhir.r4.model.*;
public class DataTypesExample {
public static void demonstrateCodeableConcept() {
// CodeableConcept - represents a coded value with multiple codings
CodeableConcept concept = new CodeableConcept();
// Add primary coding (SNOMED CT)
concept.addCoding()
.setSystem("http://snomed.info/sct")
.setCode("38341003")
.setDisplay("Hypertensive disorder");
// Add alternate coding (ICD-10)
concept.addCoding()
.setSystem("http://hl7.org/fhir/sid/icd-10")
.setCode("I10")
.setDisplay("Essential (primary) hypertension");
// Add human-readable text
concept.setText("High Blood Pressure");
System.out.println("Text: " + concept.getText());
System.out.println("Primary coding: " + concept.getCodingFirstRep().getDisplay());
}
}
Quantity Types
Various quantity types for measurements. FHIR provides specialized quantity types (Age, Duration, Distance, Count) that extend the base Quantity with semantic meaning. All quantities should use UCUM (Unified Code for Units of Measure) for units to ensure consistent interpretation. The system URI identifies the unit coding system, enabling unit conversion and comparison across systems.
public static void demonstrateQuantity() {
// Simple quantity
Quantity simpleQty = new Quantity()
.setValue(120)
.setUnit("mmHg")
.setSystem("http://unitsofmeasure.org")
.setCode("mm[Hg]");
// Age quantity
Age age = new Age()
.setValue(45)
.setUnit("years")
.setSystem("http://unitsofmeasure.org")
.setCode("a");
// Duration
Duration duration = new Duration()
.setValue(30)
.setUnit("minutes")
.setSystem("http://unitsofmeasure.org")
.setCode("min");
// Distance
Distance distance = new Distance()
.setValue(5)
.setUnit("kilometers")
.setSystem("http://unitsofmeasure.org")
.setCode("km");
// Count
Count count = new Count()
.setValue(3)
.setUnit("tablets");
}
Range
Represents a low-to-high range. Ranges are used for reference ranges in lab results, dose ranges in medication orders, and other clinical scenarios where a single value is insufficient. Both low and high values are Quantities with their own units, allowing for proper unit handling. A Range can have just a low or just a high value for open-ended ranges.
public static void demonstrateRange() {
// Range - low and high values
Range normalRange = new Range();
normalRange.setLow(new Quantity()
.setValue(70)
.setUnit("mg/dL")
.setSystem("http://unitsofmeasure.org")
.setCode("mg/dL"));
normalRange.setHigh(new Quantity()
.setValue(100)
.setUnit("mg/dL")
.setSystem("http://unitsofmeasure.org")
.setCode("mg/dL"));
}
Period
Represents a time interval with start and end. Periods are used extensively for encounter durations, coverage dates, and validity windows. Either start or end can be omitted for open-ended periods. When comparing periods, consider timezone handling and the precision of the dates (year, month, day, or full timestamp).
public static void demonstratePeriod() {
// Period - start and end dates
Period period = new Period();
period.setStart(new Date());
// 30 days from now
period.setEnd(new Date(System.currentTimeMillis() + 30L * 24 * 60 * 60 * 1000));
}
Ratio
Represents a relationship between two quantities. Ratios are essential for medication concentrations, titers, and other clinical measurements that express one quantity per another. The numerator and denominator are separate Quantities, preserving the full semantic meaning. Ratios differ from simple decimal values because they maintain the units of both components.
public static void demonstrateRatio() {
// Ratio - numerator/denominator
Ratio ratio = new Ratio();
ratio.setNumerator(new Quantity()
.setValue(5)
.setUnit("mg"));
ratio.setDenominator(new Quantity()
.setValue(1)
.setUnit("mL"));
// Represents "5 mg / 1 mL"
}
Attachment
Represents binary content or references to it. Attachments handle documents, images, and other binary data in FHIR. You can embed small content directly using base64 encoding or reference external URLs for large files. Always include the content type (MIME type) for proper handling. Consider security implications when including URLs to external content.
public static void demonstrateAttachment() {
// Attachment - binary data
Attachment attachment = new Attachment();
attachment.setContentType("application/pdf");
attachment.setTitle("Lab Report");
attachment.setCreation(new Date());
// Can include data directly (base64) or URL
attachment.setUrl("http://example.com/report.pdf");
// OR
// attachment.setData(base64EncodedData);
}
Working with Extensions
Extensions allow adding custom data to FHIR resources. FHIR’s extensibility mechanism ensures that implementations can add data elements beyond the base specification while maintaining interoperability. Extensions use URLs to identify their meaning, typically defined in StructureDefinitions. Use existing extensions from implementation guides (like US Core) when available rather than creating custom ones.
Simple Extensions
Simple extensions contain a single value of any FHIR data type. Each extension has a URL that uniquely identifies its meaning and a value element. Extensions can be added to any element, not just resources. When consuming FHIR data, always check for extensions that might contain clinically significant information.
import org.hl7.fhir.r4.model.Extension;
import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
public class ExtensionsExample {
public static void simpleExtension() {
Patient patient = new Patient();
// Add a simple extension
Extension raceExtension = new Extension();
raceExtension.setUrl("http://hl7.org/fhir/us/core/StructureDefinition/us-core-race");
raceExtension.setValue(new CodeableConcept()
.addCoding()
.setSystem("urn:oid:2.16.840.1.113883.6.238")
.setCode("2106-3")
.setDisplay("White"));
patient.addExtension(raceExtension);
}
}
Complex Extensions with Sub-extensions
Complex extensions contain nested sub-extensions rather than a single value. This pattern is common in implementation guides like US Core, where extensions like race and ethnicity require multiple related values (OMB category, detailed codes, text). Structure complex extensions carefully to match the StructureDefinition that defines them.
public static void complexExtension() {
Patient patient = new Patient();
// US Core Race Extension (complex with sub-extensions)
Extension raceExtension = new Extension();
raceExtension.setUrl("http://hl7.org/fhir/us/core/StructureDefinition/us-core-race");
// ombCategory sub-extension
raceExtension.addExtension()
.setUrl("ombCategory")
.setValue(new Coding()
.setSystem("urn:oid:2.16.840.1.113883.6.238")
.setCode("2106-3")
.setDisplay("White"));
// detailed sub-extension
raceExtension.addExtension()
.setUrl("detailed")
.setValue(new Coding()
.setSystem("urn:oid:2.16.840.1.113883.6.238")
.setCode("2108-9")
.setDisplay("European"));
// text sub-extension
raceExtension.addExtension()
.setUrl("text")
.setValue(new StringType("White"));
patient.addExtension(raceExtension);
}
Modifier Extensions
Modifier extensions affect the interpretation of the resource. Unlike regular extensions that add supplementary information, modifier extensions can change the meaning of existing elements and must be understood by consuming systems. If a system does not recognize a modifier extension, it should not process the resource because it may misinterpret the data. Use modifier extensions sparingly and only when the extension truly changes semantics.
public static void modifierExtension() {
Observation observation = new Observation();
// Modifier extensions affect the interpretation of the resource
Extension modExt = new Extension();
modExt.setUrl("http://example.org/fhir/StructureDefinition/observation-not-reliable");
modExt.setValue(new BooleanType(true));
// Use modifierExtension instead of extension
observation.addModifierExtension(modExt);
}
Retrieving Extensions
Retrieve extensions by URL to extract their values. Always check if the extension exists before accessing its value to avoid null pointer exceptions. The hasExtension method provides a quick check, while getExtensionByUrl returns the extension for value extraction. Consider creating utility methods for frequently accessed extensions.
public static void retrieveExtension() {
Patient patient = new Patient();
// Add extension
patient.addExtension()
.setUrl("http://example.org/fhir/StructureDefinition/patient-importance")
.setValue(new CodeType("VIP"));
// Retrieve extension
Extension ext = patient.getExtensionByUrl(
"http://example.org/fhir/StructureDefinition/patient-importance");
if (ext != null) {
CodeType importance = (CodeType) ext.getValue();
System.out.println("Patient importance: " + importance.getValue());
}
// Check if extension exists
boolean hasExt = patient.hasExtension(
"http://example.org/fhir/StructureDefinition/patient-importance");
}
US Core Profile Examples
US Core defines standard profiles for US healthcare that are mandated by ONC regulations. US Core profiles specify required elements, must-support flags, and standard extensions for patient, condition, observation, and other clinical resources. When building US healthcare applications, conforming to US Core ensures interoperability with EHR systems and regulatory compliance. The profile URL in meta.profile declares conformance.
public class USCoreProfileExample {
public static Patient createUSCorePatient() {
Patient patient = new Patient();
// Meta profile declaration
patient.getMeta().addProfile(
"http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient");
// Required identifier
patient.addIdentifier()
.setSystem("http://hospital.org/mrn")
.setValue("MRN-123456");
// Required name
patient.addName()
.setFamily("Smith")
.addGiven("John")
.setUse(HumanName.NameUse.OFFICIAL);
// Required gender
patient.setGender(Enumerations.AdministrativeGender.MALE);
// US Core Race Extension
Extension raceExtension = new Extension();
raceExtension.setUrl("http://hl7.org/fhir/us/core/StructureDefinition/us-core-race");
raceExtension.addExtension()
.setUrl("ombCategory")
.setValue(new Coding()
.setSystem("urn:oid:2.16.840.1.113883.6.238")
.setCode("2106-3")
.setDisplay("White"));
raceExtension.addExtension()
.setUrl("text")
.setValue(new StringType("White"));
patient.addExtension(raceExtension);
// US Core Ethnicity Extension
Extension ethnicityExtension = new Extension();
ethnicityExtension.setUrl("http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity");
ethnicityExtension.addExtension()
.setUrl("ombCategory")
.setValue(new Coding()
.setSystem("urn:oid:2.16.840.1.113883.6.238")
.setCode("2186-5")
.setDisplay("Not Hispanic or Latino"));
ethnicityExtension.addExtension()
.setUrl("text")
.setValue(new StringType("Not Hispanic or Latino"));
patient.addExtension(ethnicityExtension);
// US Core Birth Sex Extension
patient.addExtension()
.setUrl("http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex")
.setValue(new CodeType("M"));
return patient;
}
}
Common Data Type Patterns
These patterns demonstrate how to construct commonly used FHIR data types. Following these patterns ensures consistency and compliance with FHIR specifications. Each type has specific elements and use patterns that should be understood for proper clinical data representation.
HumanName
HumanName represents a person’s name with structured components. Use the appropriate NameUse (official, usual, nickname) to indicate how the name should be used. The getNameAsSingleString method provides a convenient full-name representation for display purposes.
HumanName name = new HumanName()
.setUse(HumanName.NameUse.OFFICIAL)
.setFamily("Johnson")
.addGiven("Robert")
.addGiven("James")
.addPrefix("Dr.")
.addSuffix("Jr.");
// Get full name as string
String fullName = name.getNameAsSingleString();
Address
Address represents a postal address with structured components. The use element (home, work, temp) indicates the address purpose, while type (postal, physical, both) indicates how it can be used. Multiple address lines accommodate complex addresses. Always include country for international systems.
Address address = new Address()
.setUse(Address.AddressUse.HOME)
.setType(Address.AddressType.PHYSICAL)
.addLine("123 Main Street")
.addLine("Apt 4B")
.setCity("Springfield")
.setState("IL")
.setPostalCode("62701")
.setCountry("USA");
ContactPoint
ContactPoint represents contact information including phone numbers, email addresses, and other communication channels. The system element identifies the type (phone, email, fax), while use indicates the context (home, work, mobile). Include rank when multiple contact points exist to indicate preference order.
// Phone
ContactPoint phone = new ContactPoint()
.setSystem(ContactPoint.ContactPointSystem.PHONE)
.setValue("+1-555-123-4567")
.setUse(ContactPoint.ContactPointUse.HOME);
// Email
ContactPoint email = new ContactPoint()
.setSystem(ContactPoint.ContactPointSystem.EMAIL)
.setValue("[email protected]")
.setUse(ContactPoint.ContactPointUse.WORK);
Identifier
Identifier represents a business identifier such as an MRN, SSN, or insurance member ID. The system URI provides a namespace that makes the identifier globally unique. The type element uses CodeableConcept to categorize the identifier type. Include period for identifiers that are only valid during a specific time range.
Identifier mrn = new Identifier()
.setSystem("http://hospital.org/mrn")
.setValue("MRN-123456")
.setUse(Identifier.IdentifierUse.OFFICIAL)
.setType(new CodeableConcept()
.addCoding()
.setSystem("http://terminology.hl7.org/CodeSystem/v2-0203")
.setCode("MR")
.setDisplay("Medical Record Number"));
Data Types Summary
| Category | Types |
|---|---|
| Primitive | boolean, integer, decimal, string, date, dateTime, time, instant, uri, base64Binary |
| Complex | Identifier, CodeableConcept, Coding, Quantity, Range, Period, Ratio |
| Contact | HumanName, Address, ContactPoint |
| Other | Attachment, Reference, Annotation, Signature |