Advanced FHIR Operations

Section 11 of 18
61% complete

Transaction and Batch Bundles

Transactions and batches allow multiple operations in a single request. This capability is essential for maintaining data integrity when creating related resources together, improving performance by reducing network round-trips, and implementing atomic operations where all changes must succeed or fail together.

Transaction Bundle

All operations succeed or fail together (atomic). Transactions are ideal for creating related resources like a patient with their observations and conditions. Use temporary URN references (urn:uuid:) to link resources within the transaction before server-assigned IDs exist. If any operation fails, the entire transaction rolls back, ensuring data consistency.

import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Observation;

public class TransactionBundleExample {

    private static final FhirContext ctx = FhirContext.forR4();
    private static final String serverBase = "http://hapi.fhir.org/baseR4";

    public static void executeTransaction() {
        IGenericClient client = ctx.newRestfulGenericClient(serverBase);

        // Create a transaction bundle
        Bundle bundle = new Bundle();
        bundle.setType(Bundle.BundleType.TRANSACTION);

        // Create Patient
        Patient patient = new Patient();
        patient.addName().setFamily("Transaction").addGiven("Test");
        patient.setGender(Enumerations.AdministrativeGender.MALE);

        // Add patient to bundle with temporary ID
        bundle.addEntry()
            .setFullUrl("urn:uuid:patient-1")
            .setResource(patient)
            .getRequest()
                .setMethod(Bundle.HTTPVerb.POST)
                .setUrl("Patient");

        // Create Observation referencing the patient
        Observation observation = new Observation();
        observation.setStatus(Observation.ObservationStatus.FINAL);
        observation.setSubject(new Reference("urn:uuid:patient-1"));  // Reference by temp ID
        observation.setCode(new CodeableConcept()
            .addCoding()
            .setSystem("http://loinc.org")
            .setCode("8480-6")
            .setDisplay("Systolic blood pressure"));
        observation.setValue(new Quantity()
            .setValue(120)
            .setUnit("mmHg"));

        // Add observation to bundle
        bundle.addEntry()
            .setResource(observation)
            .getRequest()
                .setMethod(Bundle.HTTPVerb.POST)
                .setUrl("Observation");

        // Execute transaction
        Bundle response = client.transaction()
            .withBundle(bundle)
            .execute();

        // Process response
        System.out.println("Transaction completed. Entries: " + response.getEntry().size());

        for (Bundle.BundleEntryComponent entry : response.getEntry()) {
            System.out.println("Created: " +
                entry.getResponse().getLocation() +
                " - Status: " + entry.getResponse().getStatus());
        }
    }
}

Batch Bundle

Each operation processed independently. Unlike transactions, batches allow some operations to succeed while others fail. Use batches when atomicity is not required but you want to reduce network round-trips. Each entry in the batch response includes its own status code, allowing you to identify and retry failed operations selectively.

public class BatchBundleExample {

    private static final FhirContext ctx = FhirContext.forR4();
    private static final String serverBase = "http://hapi.fhir.org/baseR4";

    public static void executeBatch() {
        IGenericClient client = ctx.newRestfulGenericClient(serverBase);

        // Create a batch bundle
        Bundle bundle = new Bundle();
        bundle.setType(Bundle.BundleType.BATCH);

        // Mix of operations
        // 1. Create a patient
        Patient patient = new Patient();
        patient.addName().setFamily("Batch").addGiven("Test");

        bundle.addEntry()
            .setResource(patient)
            .getRequest()
                .setMethod(Bundle.HTTPVerb.POST)
                .setUrl("Patient");

        // 2. Read an existing patient
        bundle.addEntry()
            .getRequest()
                .setMethod(Bundle.HTTPVerb.GET)
                .setUrl("Patient/example");

        // 3. Search for observations
        bundle.addEntry()
            .getRequest()
                .setMethod(Bundle.HTTPVerb.GET)
                .setUrl("Observation?_count=5");

        // 4. Delete a resource (if exists)
        bundle.addEntry()
            .getRequest()
                .setMethod(Bundle.HTTPVerb.DELETE)
                .setUrl("Patient?identifier=DELETE-ME");

        // Execute batch
        Bundle response = client.transaction()
            .withBundle(bundle)
            .execute();

        // Process response - each entry has its own outcome
        for (int i = 0; i < response.getEntry().size(); i++) {
            Bundle.BundleEntryComponent entry = response.getEntry().get(i);
            String status = entry.getResponse().getStatus();
            System.out.println("Entry " + i + ": " + status);

            // Check for errors
            if (entry.getResponse().getOutcome() != null) {
                OperationOutcome outcome =
                    (OperationOutcome) entry.getResponse().getOutcome();
                for (OperationOutcome.OperationOutcomeIssueComponent issue :
                        outcome.getIssue()) {
                    System.out.println("  Issue: " + issue.getDiagnostics());
                }
            }
        }
    }
}

History Operations

Retrieve version history of resources. FHIR servers maintain complete version history, enabling auditing, temporal queries, and recovery from mistakes. You can query history at different levels: instance (single resource), type (all resources of a type), or server (entire system). Each historical entry includes metadata about when changes occurred and what type of operation created that version.

public class HistoryOperationExample {

    private static final FhirContext ctx = FhirContext.forR4();
    private static final String serverBase = "http://hapi.fhir.org/baseR4";

    public static void getResourceHistory(String resourceType, String resourceId) {
        IGenericClient client = ctx.newRestfulGenericClient(serverBase);

        // Get history for a specific resource
        Bundle history = client.history()
            .onInstance(new IdType(resourceType, resourceId))
            .returnBundle(Bundle.class)
            .execute();

        System.out.println("Found " + history.getEntry().size() + " versions");

        for (Bundle.BundleEntryComponent entry : history.getEntry()) {
            Patient patient = (Patient) entry.getResource();
            System.out.println("Version: " + patient.getMeta().getVersionId() +
                " - Last Updated: " + patient.getMeta().getLastUpdated());
        }
    }

    public static void getTypeHistory() {
        IGenericClient client = ctx.newRestfulGenericClient(serverBase);

        // Get history for all patients (since a specific time)
        Bundle history = client.history()
            .onType(Patient.class)
            .since(new Date(System.currentTimeMillis() - 86400000))  // Last 24 hours
            .returnBundle(Bundle.class)
            .execute();

        System.out.println("Found " + history.getEntry().size() +
            " patient changes in last 24 hours");
    }

    public static void getServerHistory() {
        IGenericClient client = ctx.newRestfulGenericClient(serverBase);

        // Get history for entire server
        Bundle history = client.history()
            .onServer()
            .count(100)
            .returnBundle(Bundle.class)
            .execute();

        System.out.println("Server has " + history.getEntry().size() +
            " recent changes");
    }
}

Built-in FHIR Operations

FHIR defines several standard operations denoted by the $ prefix. Operations extend FHIR’s capabilities beyond standard CRUD, providing specialized functions for common healthcare workflows. Check the server’s CapabilityStatement to discover which operations are available.

$everything Operation

Get all resources related to a patient. The $everything operation retrieves the complete patient record including observations, conditions, medications, procedures, and other related resources in a single request. This is invaluable for patient portal applications, care coordination, and data portability scenarios. Optional parameters allow filtering by date range and resource type.

public class EverythingOperationExample {

    private static final FhirContext ctx = FhirContext.forR4();
    private static final String serverBase = "http://hapi.fhir.org/baseR4";

    public static void getPatientEverything(String patientId) {
        IGenericClient client = ctx.newRestfulGenericClient(serverBase);

        // Get everything for a patient
        Bundle everything = client.operation()
            .onInstance(new IdType("Patient", patientId))
            .named("$everything")
            .withNoParameters(Parameters.class)
            .returnResourceType(Bundle.class)
            .execute();

        System.out.println("Patient record contains " +
            everything.getEntry().size() + " resources");

        // Categorize by resource type
        Map<String, Integer> resourceCounts = new HashMap<>();

        for (Bundle.BundleEntryComponent entry : everything.getEntry()) {
            String type = entry.getResource().fhirType();
            resourceCounts.merge(type, 1, Integer::sum);
        }

        System.out.println("Resource breakdown:");
        resourceCounts.forEach((type, count) ->
            System.out.println("  " + type + ": " + count));
    }

    public static void getPatientEverythingWithFilters(String patientId) {
        IGenericClient client = ctx.newRestfulGenericClient(serverBase);

        // Get everything with filters
        Parameters params = new Parameters();
        params.addParameter().setName("start").setValue(new DateType("2023-01-01"));
        params.addParameter().setName("end").setValue(new DateType("2023-12-31"));
        params.addParameter().setName("_type")
            .setValue(new StringType("Observation,Condition,MedicationRequest"));

        Bundle everything = client.operation()
            .onInstance(new IdType("Patient", patientId))
            .named("$everything")
            .withParameters(params)
            .returnResourceType(Bundle.class)
            .execute();

        System.out.println("Filtered results: " +
            everything.getEntry().size() + " resources");
    }
}

$validate Operation

Validate resources against profiles. The $validate operation checks whether a resource conforms to FHIR specifications and optionally against specific profiles. Use this for pre-submission validation, testing data quality, or implementing validation endpoints in your applications. Server-side validation catches issues that client-side validation might miss.

public class ValidateOperationExample {

    private static final FhirContext ctx = FhirContext.forR4();
    private static final String serverBase = "http://hapi.fhir.org/baseR4";

    public static void validateResource(Patient patient) {
        IGenericClient client = ctx.newRestfulGenericClient(serverBase);

        // Validate without profile
        Parameters result = client.operation()
            .onType(Patient.class)
            .named("$validate")
            .withParameter(Parameters.class, "resource", patient)
            .execute();

        // Check result
        OperationOutcome outcome = (OperationOutcome)
            result.getParameterFirstRep().getResource();

        if (outcome != null) {
            for (OperationOutcome.OperationOutcomeIssueComponent issue :
                    outcome.getIssue()) {
                System.out.println(issue.getSeverity() + ": " +
                    issue.getDiagnostics());
            }
        }
    }

    public static void validateAgainstProfile(Patient patient, String profileUrl) {
        IGenericClient client = ctx.newRestfulGenericClient(serverBase);

        // Validate against specific profile
        Parameters params = new Parameters();
        params.addParameter().setName("resource").setResource(patient);
        params.addParameter().setName("profile").setValue(new UriType(profileUrl));

        Parameters result = client.operation()
            .onType(Patient.class)
            .named("$validate")
            .withParameters(params)
            .execute();

        // Process result
        System.out.println("Validation complete");
    }
}

$meta Operations

Manage resource metadata. The $meta, $meta-add, and $meta-delete operations work with resource tags and security labels without modifying the resource content. Tags enable workflow management (marking records for review), access control categorization, and custom classification schemes. Unlike extensions, metadata changes do not create new resource versions.

public class MetaOperationsExample {

    private static final FhirContext ctx = FhirContext.forR4();
    private static final String serverBase = "http://hapi.fhir.org/baseR4";

    public static void getResourceMeta(String patientId) {
        IGenericClient client = ctx.newRestfulGenericClient(serverBase);

        // Get meta for a resource
        Parameters result = client.operation()
            .onInstance(new IdType("Patient", patientId))
            .named("$meta")
            .withNoParameters(Parameters.class)
            .execute();

        Meta meta = (Meta) result.getParameterFirstRep().getValue();

        System.out.println("Version: " + meta.getVersionId());
        System.out.println("Last Updated: " + meta.getLastUpdated());
        System.out.println("Profiles: " + meta.getProfile());
        System.out.println("Tags: " + meta.getTag());
    }

    public static void addResourceTags(String patientId) {
        IGenericClient client = ctx.newRestfulGenericClient(serverBase);

        // Add tags to a resource
        Meta meta = new Meta();
        meta.addTag()
            .setSystem("http://hospital.org/tags")
            .setCode("VIP")
            .setDisplay("VIP Patient");

        Parameters params = new Parameters();
        params.addParameter().setName("meta").setValue(meta);

        client.operation()
            .onInstance(new IdType("Patient", patientId))
            .named("$meta-add")
            .withParameters(params)
            .execute();

        System.out.println("Tag added");
    }

    public static void deleteResourceTags(String patientId) {
        IGenericClient client = ctx.newRestfulGenericClient(serverBase);

        // Delete tags from a resource
        Meta meta = new Meta();
        meta.addTag()
            .setSystem("http://hospital.org/tags")
            .setCode("VIP");

        Parameters params = new Parameters();
        params.addParameter().setName("meta").setValue(meta);

        client.operation()
            .onInstance(new IdType("Patient", patientId))
            .named("$meta-delete")
            .withParameters(params)
            .execute();

        System.out.println("Tag deleted");
    }
}

Custom Operations

Create and call custom FHIR operations. Servers can define custom operations for specialized functionality beyond standard FHIR. Custom operations follow the same patterns as built-in operations, using Parameters for input and output. Define operations in OperationDefinition resources to document their behavior and enable discovery through the CapabilityStatement.

public class CustomOperationExample {

    private static final FhirContext ctx = FhirContext.forR4();
    private static final String serverBase = "http://hapi.fhir.org/baseR4";

    public static void invokeCustomOperation() {
        IGenericClient client = ctx.newRestfulGenericClient(serverBase);

        // Call a custom operation
        Parameters params = new Parameters();
        params.addParameter()
            .setName("input-param")
            .setValue(new StringType("value"));

        Parameters result = client.operation()
            .onServer()  // or .onType() or .onInstance()
            .named("$custom-operation")
            .withParameters(params)
            .execute();

        // Process result
        for (Parameters.ParametersParameterComponent param : result.getParameter()) {
            System.out.println(param.getName() + ": " + param.getValue());
        }
    }
}

Operations Summary Table

OperationScopeDescription
$everythingInstanceGet all resources for a patient/encounter
$validateType/InstanceValidate resource against profiles
$metaType/Instance/ServerGet metadata summary
$meta-addInstanceAdd tags/security labels
$meta-deleteInstanceRemove tags/security labels
$expandValueSetExpand a value set
$lookupCodeSystemLook up a code
$validate-codeValueSet/CodeSystemValidate a code
$translateConceptMapTranslate codes between systems
$matchPatientFind matching patients

Quiz: Advanced FHIR Operations

Question 1 of 5

What is the key difference between a transaction and a batch bundle?