FHIR Data Types and Extensions

Section 9 of 18
50% complete

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

CategoryTypes
Primitiveboolean, integer, decimal, string, date, dateTime, time, instant, uri, base64Binary
ComplexIdentifier, CodeableConcept, Coding, Quantity, Range, Period, Ratio
ContactHumanName, Address, ContactPoint
OtherAttachment, Reference, Annotation, Signature

Quiz: FHIR Data Types and Extensions

Question 1 of 5

What is the difference between a simple Extension and a Modifier Extension?