Basic Search
Search operations retrieve multiple resources matching specified criteria. FHIR search uses a powerful query syntax supporting various parameter types, modifiers, and chaining. Search results return in a Bundle with pagination links for large result sets. Understanding search is essential because it is how most applications discover and retrieve data from FHIR servers. Build queries incrementally using the fluent API for readability.
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.gclient.StringClientParam;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Patient;
public class BasicSearchExample {
private static final FhirContext ctx = FhirContext.forR4();
private static final String serverBase = "http://hapi.fhir.org/baseR4";
public static void searchByName() {
IGenericClient client = ctx.newRestfulGenericClient(serverBase);
// Search for patients by name
Bundle results = client.search()
.forResource(Patient.class)
.where(Patient.FAMILY.matches().value("Smith"))
.returnBundle(Bundle.class)
.execute();
System.out.println("Found " + results.getTotal() + " patients");
// Iterate through results
for (Bundle.BundleEntryComponent entry : results.getEntry()) {
Patient patient = (Patient) entry.getResource();
System.out.println("Patient: " +
patient.getNameFirstRep().getNameAsSingleString());
}
}
public static void searchWithMultipleParameters() {
IGenericClient client = ctx.newRestfulGenericClient(serverBase);
// Search with multiple criteria
Bundle results = client.search()
.forResource(Patient.class)
.where(Patient.FAMILY.matches().value("Smith"))
.and(Patient.GENDER.exactly().code("male"))
.and(Patient.BIRTHDATE.after().day("1980-01-01"))
.returnBundle(Bundle.class)
.execute();
System.out.println("Found " + results.getTotal() + " matching patients");
}
}
Pagination
Handle large result sets with pagination. FHIR servers typically limit results per page to prevent memory issues and improve response times. The Bundle contains next and previous links for navigation. When processing all results, iterate through pages until no next link exists. Consider using offset-based pagination for random access or the count parameter to control page size based on your use case.
public class PaginationExample {
private static final FhirContext ctx = FhirContext.forR4();
private static final String serverBase = "http://hapi.fhir.org/baseR4";
public static void searchWithPagination() {
IGenericClient client = ctx.newRestfulGenericClient(serverBase);
// First page
Bundle results = client.search()
.forResource(Patient.class)
.count(10) // Page size
.returnBundle(Bundle.class)
.execute();
System.out.println("Page 1: " + results.getEntry().size() + " results");
// Get next page
if (results.getLink(Bundle.LINK_NEXT) != null) {
Bundle nextPage = client.loadPage()
.next(results)
.execute();
System.out.println("Page 2: " + nextPage.getEntry().size() + " results");
}
}
public static void iterateAllPages() {
IGenericClient client = ctx.newRestfulGenericClient(serverBase);
Bundle results = client.search()
.forResource(Patient.class)
.count(20)
.returnBundle(Bundle.class)
.execute();
int totalProcessed = 0;
int pageNumber = 1;
do {
System.out.println("Processing page " + pageNumber);
for (Bundle.BundleEntryComponent entry : results.getEntry()) {
Patient patient = (Patient) entry.getResource();
// Process patient
totalProcessed++;
}
// Load next page if available
if (results.getLink(Bundle.LINK_NEXT) != null) {
results = client.loadPage().next(results).execute();
pageNumber++;
} else {
results = null;
}
} while (results != null);
System.out.println("Total processed: " + totalProcessed);
}
}
Includes and Reverse Includes
Include related resources in search results to reduce round trips. The _include parameter retrieves resources that the search results reference (forward direction), while _revinclude retrieves resources that reference the search results (reverse direction). For example, when searching patients, use _revinclude to fetch their observations in the same request. This dramatically improves performance compared to making separate requests for each reference.
public class IncludesExample {
private static final FhirContext ctx = FhirContext.forR4();
private static final String serverBase = "http://hapi.fhir.org/baseR4";
public static void searchWithIncludes() {
IGenericClient client = ctx.newRestfulGenericClient(serverBase);
// Include related resources
Bundle results = client.search()
.forResource(Observation.class)
.where(Observation.PATIENT.hasId("example"))
.include(Observation.INCLUDE_PATIENT) // Include the patient
.include(Observation.INCLUDE_PERFORMER) // Include performer
.returnBundle(Bundle.class)
.execute();
for (Bundle.BundleEntryComponent entry : results.getEntry()) {
if (entry.getResource() instanceof Observation) {
System.out.println("Observation: " + entry.getResource().getId());
} else if (entry.getResource() instanceof Patient) {
System.out.println("Included Patient: " + entry.getResource().getId());
}
}
}
public static void searchWithRevIncludes() {
IGenericClient client = ctx.newRestfulGenericClient(serverBase);
// Get patient and all related observations
Bundle results = client.search()
.forResource(Patient.class)
.where(Patient.RES_ID.exactly().code("example"))
.revInclude(Observation.INCLUDE_PATIENT)
.returnBundle(Bundle.class)
.execute();
System.out.println("Found patient with " +
(results.getEntry().size() - 1) + " related observations");
}
}
Sorting
Sort search results by specific fields. Sorting ensures predictable, reproducible result ordering which is important for paginated displays and synchronization workflows. You can sort by multiple fields with ascending or descending order. Not all search parameters support sorting, so check server capability statements when designing queries that depend on specific ordering.
public class SortingExample {
private static final FhirContext ctx = FhirContext.forR4();
private static final String serverBase = "http://hapi.fhir.org/baseR4";
public static void searchWithSort() {
IGenericClient client = ctx.newRestfulGenericClient(serverBase);
// Sort by birth date descending
Bundle results = client.search()
.forResource(Patient.class)
.sort().descending(Patient.BIRTHDATE)
.returnBundle(Bundle.class)
.execute();
for (Bundle.BundleEntryComponent entry : results.getEntry()) {
Patient patient = (Patient) entry.getResource();
System.out.println("Patient: " +
patient.getNameFirstRep().getNameAsSingleString() +
" - DOB: " + patient.getBirthDate());
}
}
public static void multipleSort() {
IGenericClient client = ctx.newRestfulGenericClient(serverBase);
// Sort by family name ascending, then birth date descending
Bundle results = client.search()
.forResource(Patient.class)
.sort().ascending(Patient.FAMILY)
.sort().descending(Patient.BIRTHDATE)
.returnBundle(Bundle.class)
.execute();
}
}
Date and Quantity Searches
Search using date ranges and quantity comparisons. Date parameters support comparison operators (greater than, less than, equals) and can match partial dates. Quantity searches include unit-aware comparisons essential for clinical data like lab values. Combine multiple date criteria to create date ranges, which is common for filtering clinical events within a time period.
import ca.uhn.fhir.rest.gclient.DateClientParam;
import ca.uhn.fhir.rest.gclient.NumberClientParam;
public class DateQuantitySearchExample {
private static final FhirContext ctx = FhirContext.forR4();
private static final String serverBase = "http://hapi.fhir.org/baseR4";
public static void searchByDateRange() {
IGenericClient client = ctx.newRestfulGenericClient(serverBase);
// Search for observations in a date range
Bundle results = client.search()
.forResource(Observation.class)
.where(Observation.DATE.afterOrEquals().day("2023-01-01"))
.and(Observation.DATE.beforeOrEquals().day("2023-12-31"))
.returnBundle(Bundle.class)
.execute();
System.out.println("Found " + results.getTotal() + " observations in 2023");
}
public static void searchByQuantity() {
IGenericClient client = ctx.newRestfulGenericClient(serverBase);
// Search for observations with value > 100
Bundle results = client.search()
.forResource(Observation.class)
.where(Observation.VALUE_QUANTITY.greaterThan()
.number(100)
.andUnits("http://unitsofmeasure.org", "mg/dL"))
.returnBundle(Bundle.class)
.execute();
System.out.println("Found high values: " + results.getTotal());
}
public static void searchByCodeSystem() {
IGenericClient client = ctx.newRestfulGenericClient(serverBase);
// Search using specific coding system
Bundle results = client.search()
.forResource(Observation.class)
.where(Observation.CODE.exactly()
.systemAndCode("http://loinc.org", "2339-0")) // Glucose
.returnBundle(Bundle.class)
.execute();
System.out.println("Found glucose observations: " + results.getTotal());
}
}
Token and Reference Searches
Search by identifiers and references. Token searches find resources by coded values and identifiers using system|value notation. Reference searches find resources that reference specific entities. Chained searches (patient.name) traverse references to search on properties of linked resources, enabling powerful queries like “find observations where the patient’s name is Smith” without knowing the patient ID.
public class TokenReferenceSearchExample {
private static final FhirContext ctx = FhirContext.forR4();
private static final String serverBase = "http://hapi.fhir.org/baseR4";
public static void searchByIdentifier() {
IGenericClient client = ctx.newRestfulGenericClient(serverBase);
// Search by identifier
Bundle results = client.search()
.forResource(Patient.class)
.where(Patient.IDENTIFIER.exactly()
.systemAndIdentifier("http://hospital.org/mrn", "MRN-123456"))
.returnBundle(Bundle.class)
.execute();
if (results.getEntry().size() > 0) {
Patient patient = (Patient) results.getEntry().get(0).getResource();
System.out.println("Found patient: " +
patient.getNameFirstRep().getNameAsSingleString());
}
}
public static void searchByReference() {
IGenericClient client = ctx.newRestfulGenericClient(serverBase);
// Search for all observations for a specific patient
Bundle results = client.search()
.forResource(Observation.class)
.where(Observation.PATIENT.hasId("Patient/example"))
.returnBundle(Bundle.class)
.execute();
System.out.println("Found " + results.getTotal() + " observations");
}
public static void chainedSearch() {
IGenericClient client = ctx.newRestfulGenericClient(serverBase);
// Search for observations where the patient's name is "Smith"
Bundle results = client.search()
.forResource(Observation.class)
.where(Observation.PATIENT.hasChainedProperty(
Patient.FAMILY.matches().value("Smith")))
.returnBundle(Bundle.class)
.execute();
System.out.println("Found observations for patients named Smith: " +
results.getTotal());
}
}
Complex Query Example
Combine multiple search features for comprehensive queries. Real-world applications typically need multiple filters, includes, sorting, and pagination in a single query. Build these queries incrementally, testing each addition. The elementsSubset feature can further optimize by returning only needed fields. Always implement proper pagination handling for production code to process all matching results.
public class ComplexQueryExample {
private static final FhirContext ctx = FhirContext.forR4();
private static final String serverBase = "http://hapi.fhir.org/baseR4";
public static void comprehensiveSearch() {
IGenericClient client = ctx.newRestfulGenericClient(serverBase);
// Complex multi-criteria search
Bundle results = client.search()
.forResource(Observation.class)
// Search parameters
.where(Observation.CATEGORY.exactly().code("vital-signs"))
.and(Observation.CODE.exactly()
.systemAndCode("http://loinc.org", "85354-9")) // Blood pressure
.and(Observation.DATE.afterOrEquals().day("2023-01-01"))
.and(Observation.PATIENT.hasChainedProperty(
Patient.ACTIVE.exactly().code("true")))
// Include related resources
.include(Observation.INCLUDE_PATIENT)
.include(Observation.INCLUDE_PERFORMER)
// Sorting
.sort().descending(Observation.DATE)
// Pagination
.count(20)
// Return elements
.elementsSubset("code", "effectiveDateTime", "component", "subject")
// Execute
.returnBundle(Bundle.class)
.execute();
System.out.println("Complex search found: " + results.getTotal() + " results");
// Process results
for (Bundle.BundleEntryComponent entry : results.getEntry()) {
if (entry.getResource() instanceof Observation) {
Observation obs = (Observation) entry.getResource();
System.out.println("Observation " + obs.getId() +
" - Date: " + obs.getEffectiveDateTimeType().getValueAsString());
}
}
// Handle pagination
while (results.getLink(Bundle.LINK_NEXT) != null) {
results = client.loadPage().next(results).execute();
System.out.println("Next page: " + results.getEntry().size() + " entries");
}
}
}
Search Parameter Types
| Type | Description | Example |
|---|---|---|
| String | Text matching | name=Smith |
| Token | Code/identifier matching | identifier=MRN-123 |
| Reference | Resource reference | patient=Patient/123 |
| Date | Date/time matching | date=gt2023-01-01 |
| Quantity | Numeric with units | value-quantity=gt100 |
| Number | Simple numeric | length=gt10 |
| URI | URI matching | url=http://example.com |
| Composite | Combined parameters | code-value-quantity |
Search Modifiers
| Modifier | Description |
|---|---|
:exact | Exact string match |
:contains | Substring match |
:missing | Check for missing values |
:not | Negation |
:text | Text search on CodeableConcept |
:above | Hierarchical code search (above) |
:below | Hierarchical code search (below) |
Related Articles
Explore FHIR search capabilities with these practical tutorials:
- FHIR Programming using Java HAPI - Searching FHIR Resources - Comprehensive search examples
- FHIR Programming using .NET - Searching FHIR Resources - .NET search implementation
- FHIR Programming using Java HAPI - Chaining FHIR Operations - Advanced query patterns
- FHIR Programming using .NET - Chaining FHIR Operations - Complex query strategies