HAPI FHIR Library Fundamentals

Section 5 of 18
28% complete

FhirContext - The Foundation

The FhirContext is the central starting point for all HAPI FHIR operations. It’s a thread-safe, heavyweight object that should be created once and reused. During initialization, FhirContext loads and parses all FHIR resource definitions, which is a memory-intensive and time-consuming process. By creating a single instance and sharing it across your application, you avoid this overhead and improve performance significantly. Consider storing it as a static final field or using dependency injection.

import ca.uhn.fhir.context.FhirContext;

public class FhirContextExample {
    // Create a context for R4 - do this once!
    private static final FhirContext ctx = FhirContext.forR4();

    // For R5
    // private static final FhirContext ctx = FhirContext.forR5();

    // For DSTU3
    // private static final FhirContext ctx = FhirContext.forDstu3();

    public static FhirContext getContext() {
        return ctx;
    }
}

Important: Creating a FhirContext is expensive. Always reuse it!

Creating a FHIR Client

The FHIR client allows you to interact with FHIR servers. HAPI FHIR provides IGenericClient, a fluent API for all standard FHIR operations including create, read, update, delete, and search. You can configure timeouts, disable server validation for faster startup, and add interceptors for logging, authentication, or custom header injection. The client handles JSON/XML serialization, HTTP communication, and error handling automatically.

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;

public class FhirClientFactory {

    private static final FhirContext ctx = FhirContext.forR4();

    public static IGenericClient createClient(String serverBase) {
        // Disable server validation (optional - for faster startup)
        ctx.getRestfulClientFactory()
           .setServerValidationMode(ServerValidationModeEnum.NEVER);

        // Set connection timeout (optional)
        ctx.getRestfulClientFactory().setConnectTimeout(20 * 1000);
        ctx.getRestfulClientFactory().setSocketTimeout(20 * 1000);

        // Create the client
        IGenericClient client = ctx.newRestfulGenericClient(serverBase);

        // Add logging interceptor (optional - for debugging)
        LoggingInterceptor loggingInterceptor = new LoggingInterceptor();
        loggingInterceptor.setLogRequestSummary(true);
        loggingInterceptor.setLogRequestBody(true);
        client.registerInterceptor(loggingInterceptor);

        return client;
    }

    public static void main(String[] args) {
        // Example: Connect to public HAPI test server
        String serverBase = "http://hapi.fhir.org/baseR4";
        IGenericClient client = createClient(serverBase);

        System.out.println("FHIR Client created successfully!");
        System.out.println("Server: " + serverBase);
    }
}

Parsing and Encoding Resources

HAPI FHIR provides parsers for both JSON and XML formats. Parsers are lightweight objects that can be created as needed, unlike FhirContext which should be reused. Encoding converts FHIR resources to string representations for transmission or storage, while parsing reconstructs resources from these strings. HAPI handles all the complexity of serialization, including extensions, contained resources, and proper formatting.

JSON Parsing and Encoding

JSON is the most common format for modern FHIR applications due to its compactness and JavaScript compatibility. The JSON parser creates human-readable output when pretty-printing is enabled, making it easier to debug and inspect resources during development.

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.parser.IParser;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.HumanName;
import org.hl7.fhir.r4.model.Enumerations.AdministrativeGender;

public class ParsingExample {

    private static final FhirContext ctx = FhirContext.forR4();

    public static void demonstrateParsing() {
        // Create a patient
        Patient patient = new Patient();
        patient.addName()
            .setFamily("Smith")
            .addGiven("John")
            .addGiven("Michael");
        patient.setGender(AdministrativeGender.MALE);

        // Create a JSON parser
        IParser jsonParser = ctx.newJsonParser();
        jsonParser.setPrettyPrint(true);

        // Encode to JSON
        String jsonString = jsonParser.encodeResourceToString(patient);
        System.out.println("Encoded Patient (JSON):");
        System.out.println(jsonString);

        // Parse back from JSON
        Patient parsedPatient = jsonParser.parseResource(Patient.class, jsonString);
        System.out.println("\nParsed patient name: " +
            parsedPatient.getNameFirstRep().getNameAsSingleString());
    }
}

XML Parsing and Encoding

XML remains important for systems that require it, particularly legacy healthcare systems and certain government regulations. The XML parser follows the same API as the JSON parser, making it easy to switch formats when needed. Both parsers produce fully compliant FHIR output that can be validated against the specification.

public class XmlParsingExample {

    private static final FhirContext ctx = FhirContext.forR4();

    public static void demonstrateXmlParsing() {
        Patient patient = new Patient();
        patient.addName().setFamily("Doe").addGiven("Jane");

        // Create an XML parser
        IParser xmlParser = ctx.newXmlParser();
        xmlParser.setPrettyPrint(true);

        // Encode to XML
        String xmlString = xmlParser.encodeResourceToString(patient);
        System.out.println("Encoded Patient (XML):");
        System.out.println(xmlString);

        // Parse back from XML
        Patient parsedPatient = xmlParser.parseResource(Patient.class, xmlString);
        System.out.println("\nParsed patient: " +
            parsedPatient.getNameFirstRep().getNameAsSingleString());
    }
}

Parser Options

HAPI FHIR parsers offer many configuration options to control output format and content. These options help optimize bandwidth by removing unnecessary elements, protect privacy by excluding sensitive data, and improve readability for debugging. Understanding these options is important for production systems where performance and data minimization matter.

public class ParserOptionsExample {

    private static final FhirContext ctx = FhirContext.forR4();

    public static void demonstrateParserOptions() {
        IParser parser = ctx.newJsonParser();

        // Pretty printing
        parser.setPrettyPrint(true);

        // Summary mode (omit large/narrative elements)
        parser.setSummaryMode(true);

        // Suppress narrative
        parser.setSuppressNarratives(true);

        // Override resource ID (useful for bundles)
        parser.setOverrideResourceIdWithBundleEntryFullUrl(true);

        // Don't encode certain elements
        parser.setDontEncodeElements(Set.of("text", "meta"));

        // Encode specific elements only
        parser.setEncodeElements(Set.of("Patient.name", "Patient.gender"));
    }
}

Client Interceptors

Interceptors allow you to modify requests and responses transparently. They follow the interceptor pattern, enabling cross-cutting concerns like authentication, logging, and metrics collection without modifying your core business logic. HAPI provides built-in interceptors for common needs like basic auth and bearer tokens, and you can create custom interceptors for specialized requirements like request signing or audit logging.

import ca.uhn.fhir.rest.client.interceptor.BasicAuthInterceptor;
import ca.uhn.fhir.rest.client.interceptor.BearerTokenAuthInterceptor;

public class InterceptorExample {

    public static IGenericClient createAuthenticatedClient(String serverBase) {
        FhirContext ctx = FhirContext.forR4();
        IGenericClient client = ctx.newRestfulGenericClient(serverBase);

        // Basic authentication
        client.registerInterceptor(
            new BasicAuthInterceptor("username", "password")
        );

        // Or Bearer token authentication
        // client.registerInterceptor(
        //     new BearerTokenAuthInterceptor("your-oauth-token")
        // );

        return client;
    }
}

Best Practices Summary

PracticeRecommendation
FhirContextCreate once, reuse everywhere
ParsersCreate as needed, they’re lightweight
ClientsReuse when possible
InterceptorsAdd logging in development
TimeoutsAlways configure appropriate timeouts

Explore these practical FHIR programming tutorials for hands-on examples:

Quiz: HAPI FHIR Library Fundamentals

Question 1 of 4

What is the recommended way to create and use FhirContext?