FHIR RESTful API Operations

Section 7 of 18
39% complete

Create Operation (POST)

The Create operation adds a new resource to the FHIR server. The server assigns the resource ID. Create uses HTTP POST and returns a MethodOutcome containing the assigned ID and creation status. For idempotent operations (preventing duplicates), use conditional create which only creates the resource if no matching resource exists. The returned MethodOutcome also includes the complete created resource if requested.

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.api.MethodOutcome;
import org.hl7.fhir.r4.model.*;

public class CreateOperationExample {

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

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

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

        // Execute create
        MethodOutcome outcome = client.create()
            .resource(patient)
            .execute();

        // Get the ID of the created resource
        IdType id = (IdType) outcome.getId();
        System.out.println("Created Patient with ID: " + id.getIdPart());

        // Check if resource was created
        System.out.println("Was created: " + outcome.getCreated());

        // Get the created resource
        Patient createdPatient = (Patient) outcome.getResource();
        if (createdPatient != null) {
            System.out.println("Created patient: " +
                createdPatient.getNameFirstRep().getNameAsSingleString());
        }
    }

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

        Patient patient = new Patient();
        patient.addIdentifier()
            .setSystem("http://hospital.org/mrn")
            .setValue("MRN-987654");
        patient.addName()
            .setFamily("Conditional")
            .addGiven("Test");

        // Conditional create: only create if no patient with this identifier exists
        MethodOutcome outcome = client.create()
            .resource(patient)
            .conditional()
            .where(Patient.IDENTIFIER.exactly()
                .systemAndIdentifier("http://hospital.org/mrn", "MRN-987654"))
            .execute();

        System.out.println("Created: " + outcome.getCreated());
        System.out.println("Resource ID: " + outcome.getId().getIdPart());
    }

    public static void main(String[] args) {
        createPatient();
        createWithConditional();
    }
}

Read Operation (GET)

The Read operation retrieves a resource by its ID. FHIR supports reading the current version, a specific historical version, or a subset of elements for bandwidth optimization. Always handle ResourceNotFoundException for missing resources. The elementsSubset feature is particularly useful for mobile applications or dashboards where you only need specific fields like name and identifier.

import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;

public class ReadOperationExample {

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

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

        try {
            // Read by ID
            Patient patient = client.read()
                .resource(Patient.class)
                .withId(patientId)
                .execute();

            System.out.println("Patient Name: " +
                patient.getNameFirstRep().getNameAsSingleString());
            System.out.println("Gender: " + patient.getGender());
            System.out.println("Birth Date: " + patient.getBirthDate());

        } catch (ResourceNotFoundException e) {
            System.err.println("Patient not found: " + patientId);
        }
    }

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

        // Read specific version
        Patient patient = client.read()
            .resource(Patient.class)
            .withId(patientId)
            .withVersion(versionId)
            .execute();

        System.out.println("Retrieved version " + versionId);
    }

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

        // Read only specific elements
        Patient patient = client.read()
            .resource(Patient.class)
            .withId("example")
            .elementsSubset("name", "gender", "birthDate")
            .execute();

        // Only requested elements will be populated
        System.out.println("Name: " + patient.getNameFirstRep().getNameAsSingleString());
    }
}

Update Operation (PUT)

The Update operation replaces an existing resource entirely using HTTP PUT. The resource ID must be included in the resource body and match the URL. Updates are versioned, meaning the server creates a new version rather than overwriting. Conditional updates allow updating based on search criteria rather than ID, which is useful when you have business identifiers but not FHIR IDs. The server increments the version ID with each update.

public class UpdateOperationExample {

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

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

        // First, read the existing patient
        Patient patient = client.read()
            .resource(Patient.class)
            .withId(patientId)
            .execute();

        // Modify the patient
        patient.addTelecom()
            .setSystem(ContactPoint.ContactPointSystem.EMAIL)
            .setValue("[email protected]");

        // Update the resource
        MethodOutcome outcome = client.update()
            .resource(patient)
            .execute();

        System.out.println("Updated patient. New version: " +
            outcome.getId().getVersionIdPart());
    }

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

        Patient patient = new Patient();
        patient.addIdentifier()
            .setSystem("http://hospital.org/mrn")
            .setValue("MRN-UPDATE-TEST");
        patient.addName()
            .setFamily("Updated")
            .addGiven("Conditionally");

        // Update patient with specific identifier
        MethodOutcome outcome = client.update()
            .resource(patient)
            .conditional()
            .where(Patient.IDENTIFIER.exactly()
                .systemAndIdentifier("http://hospital.org/mrn", "MRN-UPDATE-TEST"))
            .execute();

        System.out.println("Updated: " + !outcome.getCreated());
    }
}

Patch Operation

The Patch operation allows partial updates to a resource without sending the entire resource content. This is more efficient than PUT for small changes and reduces the risk of overwriting concurrent updates. FHIR supports two patch formats: JSON Patch (RFC 6902) using operations like add, replace, and remove, and FHIRPath Patch using FHIR-native Parameters resources. Choose JSON Patch for its simplicity or FHIRPath Patch when you need FHIR-aware operations.

import ca.uhn.fhir.rest.api.PatchTypeEnum;
import org.hl7.fhir.instance.model.api.IBaseResource;

public class PatchOperationExample {

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

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

        // JSON Patch format
        String patchBody = """
            [
                {
                    "op": "replace",
                    "path": "/active",
                    "value": false
                },
                {
                    "op": "add",
                    "path": "/telecom/-",
                    "value": {
                        "system": "email",
                        "value": "[email protected]"
                    }
                }
            ]
            """;

        MethodOutcome outcome = client.patch()
            .withBody(patchBody)
            .withId(patientId)
            .execute();

        System.out.println("Patched patient: " + outcome.getId().getIdPart());
    }

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

        // FHIRPath Patch (Parameters resource)
        Parameters patch = new Parameters();

        // Delete operation
        patch.addParameter()
            .setName("operation")
            .addPart(new Parameters.ParametersParameterComponent()
                .setName("type")
                .setValue(new CodeType("delete")))
            .addPart(new Parameters.ParametersParameterComponent()
                .setName("path")
                .setValue(new StringType("Patient.telecom[0]")));

        // Add operation
        patch.addParameter()
            .setName("operation")
            .addPart(new Parameters.ParametersParameterComponent()
                .setName("type")
                .setValue(new CodeType("add")))
            .addPart(new Parameters.ParametersParameterComponent()
                .setName("path")
                .setValue(new StringType("Patient")))
            .addPart(new Parameters.ParametersParameterComponent()
                .setName("name")
                .setValue(new StringType("active")))
            .addPart(new Parameters.ParametersParameterComponent()
                .setName("value")
                .setValue(new BooleanType(true)));

        MethodOutcome outcome = client.patch()
            .withFhirPatch(patch)
            .withId(patientId)
            .execute();

        System.out.println("FHIRPath patched patient");
    }
}

Delete Operation

The Delete operation removes a resource from the server. In FHIR, delete is typically a logical delete preserving version history rather than a physical delete. Deleted resources return 410 Gone when accessed. Conditional delete removes resources matching search criteria, useful for cleanup operations. Some servers support cascade delete for resources with dependencies, though this requires careful consideration of referential integrity.

public class DeleteOperationExample {

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

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

        try {
            MethodOutcome outcome = client.delete()
                .resourceById("Patient", patientId)
                .execute();

            System.out.println("Patient deleted successfully");

        } catch (Exception e) {
            System.err.println("Delete failed: " + e.getMessage());
        }
    }

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

        // Delete all patients with a specific identifier
        MethodOutcome outcome = client.delete()
            .resourceConditionalByType("Patient")
            .where(Patient.IDENTIFIER.exactly()
                .systemAndIdentifier("http://hospital.org/mrn", "DELETE-ME"))
            .execute();

        System.out.println("Conditional delete executed");
    }

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

        // Delete with cascade (implementation-specific)
        // Some servers support _cascade parameter
        MethodOutcome outcome = client.delete()
            .resourceById("Patient", patientId)
            .withAdditionalHeader("X-Cascade", "delete")
            .execute();

        System.out.println("Cascade delete executed");
    }
}

HTTP Methods Summary

OperationHTTP MethodDescription
CreatePOSTCreate new resource (server assigns ID)
ReadGETRetrieve resource by ID
UpdatePUTReplace entire resource
PatchPATCHPartial update
DeleteDELETERemove resource
SearchGETQuery for resources
HistoryGETGet version history

Best Practices

  1. Always handle exceptions: Wrap operations in try-catch blocks
  2. Use conditional operations: Prevent duplicates with conditional create/update
  3. Prefer PATCH over PUT: For small changes, PATCH is more efficient
  4. Check MethodOutcome: Always verify operation success through the outcome
  5. Use version IDs: For optimistic locking in concurrent environments

Learn more about FHIR CRUD operations with practical examples:

Quiz: FHIR RESTful API Operations

Question 1 of 5

Which HTTP method is used for the FHIR Create operation?