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
| Operation | Scope | Description |
|---|---|---|
$everything | Instance | Get all resources for a patient/encounter |
$validate | Type/Instance | Validate resource against profiles |
$meta | Type/Instance/Server | Get metadata summary |
$meta-add | Instance | Add tags/security labels |
$meta-delete | Instance | Remove tags/security labels |
$expand | ValueSet | Expand a value set |
$lookup | CodeSystem | Look up a code |
$validate-code | ValueSet/CodeSystem | Validate a code |
$translate | ConceptMap | Translate codes between systems |
$match | Patient | Find matching patients |