HAPI provides a built-in mechanism for connecting to FHIR RESTful servers. The HAPI RESTful client is designed to be easy to set up and to allow strong compile-time type checking wherever possible.

There are two types of RESTful clients provided by HAPI: The Fluent/Generic client (described below) and the Annotation client. The generic client is simpler to use and generally provides the faster way to get started. The annotation-driven client relies on static binding to specific operations to give better compile-time checking against servers with a specific set of capabilities exposed. This second model takes more effort to use, but can be useful if the person defining the specific methods to invoke is not the same person who is using those methods.

The Fluent/Generic Client

Creating a generic client simply requires you to create an instance of FhirContext and use that to instantiate a client.

The following example shows how to create a client, and a few operations which can be performed.

      // We're connecting to a DSTU1 compliant server in this example
      FhirContext ctx = FhirContext.forDstu2();
      String serverBase = "http://fhirtest.uhn.ca/baseDstu2";
      
      IGenericClient client = ctx.newRestfulGenericClient(serverBase);

      // Perform a search
      Bundle results = client
            .search()
            .forResource(Patient.class)
            .where(Patient.FAMILY.matches().value("duck"))
            .returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class)
            .execute();

      System.out.println("Found " + results.getEntry().size() + " patients named 'duck'");

Performance Tip: Note that FhirContext is an expensive object to create, so you should try to keep an instance around for the lifetime of your application. It is thread-safe so it can be passed as needed. Client instances, on the other hand, are very inexpensive to create so you can create a new one for each request if needed (although there is no requirement to do so, clients are reusable and thread-safe as well).

Fluent Calls

The generic client supports queries using a fluent interface which is inspired by the fantastic .NET FHIR API. The fluent interface allows you to construct powerful queries by chaining method calls together, leading to highly readable code. It also allows you to take advantage of intellisense/code completion in your favourite IDE.

Note that most fluent operations end with an execute() statement which actually performs the invocation. You may also invoke several configuration operations just prior to the execute() statement, such as encodedJson() or encodedXml().

Search/Query - Type

Searching for resources is probably the most common initial scenario for client applications, so we'll start the demonstration there. The FHIR search operation generally uses a URL with a set of predefined search parameters, and returns a Bundle containing zero-or-more resources which matched the given search criteria.

Search is a very powerful mechanism, with advanced features such as paging, including linked resources, etc. See the FHIR search specification for more information.

Note on Bundle types: As of DSTU2, FHIR defines Bundle as a resource instead of an Atom feed as it was in DSTU1. In code that was written for DSTU1 you would typically use the ca.uhn.fhir.model.api.Bundle class to represent a bundle, and that is that default return type for search methods. If you are implemeting a DSTU2+ server, is recommended to use a Bundle resource class instead (e.g. ca.uhn.fhir.model.dstu2.resource.Bundle or org.hl7.fhir.instance.model.Bundle). Many of the examples below include a chained invocation similar to .returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class), which instructs the search method which bundle type should be returned.

The following example shows how to query using the generic client:

ca.uhn.fhir.model.dstu2.resource.Bundle response = client.search()
      .forResource(Patient.class)
      .where(Patient.BIRTHDATE.beforeOrEquals().day("2011-01-01"))
      .and(Patient.CAREPROVIDER.hasChainedProperty(Organization.NAME.matches().value("Health")))
      .returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class)
      .execute();

Search - Multi-valued Parameters (ANY/OR)

To search for a set of possible values where ANY should be matched, you can provide multiple values to a parameter, as shown in the example below. This leads to a URL resembling ?family=Smith,Smyth

response = client.search()
      .forResource(Patient.class)
      .where(Patient.FAMILY.matches().values("Smith", "Smyth"))
      .returnBundle(Bundle.class)
      .execute();

Search - Multi-valued Parameters (ALL/AND)

To search for a set of possible values where ALL should be matched, you can provide multiple instances of a parameter, as shown in the example below. This leads to a URL resembling ?address=Toronto&address=Ontario&address=Canada

response = client.search()
      .forResource(Patient.class)
      .where(Patient.ADDRESS.matches().values("Toronto"))
      .and(Patient.ADDRESS.matches().values("Ontario"))
      .and(Patient.ADDRESS.matches().values("Canada"))
      .returnBundle(Bundle.class)
      .execute();

Search - Paging

If the server supports paging results, the client has a page method which can be used to load subsequent pages.

         FhirContext ctx = FhirContext.forDstu2();
         IGenericClient client = ctx.newRestfulGenericClient("http://fhirtest.uhn.ca/baseDstu2");
         
         // Perform a search
         Bundle resultBundle = client.search()
               .forResource(Patient.class)
               .where(Patient.NAME.matches().value("Smith"))
               .returnBundle(Bundle.class)
               .execute();
         
         if (resultBundle.getLink(Bundle.LINK_NEXT) != null) {

            // load next page
            Bundle nextPage = client.loadPage().next(resultBundle).execute();
         }

Search - Composite Parameters

If a composite parameter is being searched on, the parameter takes a "left" and "right" operand, each of which is a parameter from the resource being seached. The following example shows the syntax.

response = client.search()
      .forResource("Observation")
      .where(Observation.CODE_VALUE_DATE
            .withLeft(Observation.CODE.exactly().code("FOO$BAR"))
            .withRight(Observation.VALUE_DATE.exactly().day("2001-01-01")))
      .returnBundle(Bundle.class)
      .execute();

Search - By plain URL

You can also perform a search using a String URL, instead of using the fluent method calls to build the URL. This can be useful if you have a URL you retrieved from somewhere else that you want to use as a search.

String searchUrl = "http://example.com/base/Patient?identifier=foo";

// Search URL can also be a relative URL in which case the client's base
// URL will be added to it
searchUrl = "Patient?identifier=foo";

response = client.search()
      .byUrl(searchUrl)
      .returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class)
      .execute();

Search - Other Query Options

The fluent search also has methods for sorting, limiting, specifying JSON encoding, _include, _revinclude, _lastUpdated, _tag, etc.

response = client.search()
      .forResource(Patient.class)
      .encodedJson()
      .where(Patient.BIRTHDATE.beforeOrEquals().day("2012-01-22"))
      .and(Patient.BIRTHDATE.after().day("2011-01-01"))
      .withTag("http://acme.org/codes", "needs-review")
      .include(Patient.INCLUDE_ORGANIZATION.asRecursive())
      .include(Patient.INCLUDE_CAREPROVIDER.asNonRecursive())
      .revInclude(Provenance.INCLUDE_TARGET)
      .lastUpdated(new DateRangeParam("2011-01-01", null))
      .sort().ascending(Patient.BIRTHDATE)
      .sort().descending(Patient.NAME).limitTo(123)
      .returnBundle(Bundle.class)
      .execute();

Search - Using HTTP POST

The FHIR specification allows the use of an HTTP POST to transmit a search to a server instead of using an HTTP GET. With this style of search, the search parameters are included in the request body instead of the request URL, which can be useful if you need to transmit a search with a large number of parameters.

The usingStyle() method controls which style to use. By default, GET style is used unless the client detects that the request would result in a very long URL (over 8000 chars) in which case the client automatically switches to POST.

An alternate form of the search URL (using a URL ending with_search) was also supported in FHIR DSTU1. This form is no longer valid in FHIR DSTU2, but HAPI retains support for using this form in order to interoperate with servers which use it.

response = client.search()
      .forResource("Patient")
      .where(Patient.NAME.matches().value("Tester"))
      .usingStyle(SearchStyleEnum.POST)
      .returnBundle(Bundle.class)
      .execute();

Search - Compartments

To search a resource compartment, simply use the withIdAndCompartment method in your search.

response = client.search()
      .forResource(Patient.class)
      .withIdAndCompartment("123", "condition")
      .where(Patient.ADDRESS.matches().values("Toronto"))
      .returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class)
      .execute();

Search - Subsetting (_summary and _elements)

Sometimes you may want to only ask the server to include some parts of returned resources (instead of the whole resource). Typically this is for performance or optimization reasons, but there may also be privacy reasons for doing this.

To request that the server return only "summary" elements (those elements defined in the specification with the "Σ" flag), you can use the summaryMode(SummaryEnum) qualifier:

response = client.search()
      .forResource(Patient.class)
      .where(Patient.ADDRESS.matches().values("Toronto"))
      .returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class)
      .summaryMode(SummaryEnum.TRUE)
      .execute();

To request that the server return only elements from a custom list provided by the client, you can use the elementsSubset(String...) qualifier:

response = client.search()
      .forResource(Patient.class)
      .where(Patient.ADDRESS.matches().values("Toronto"))
      .returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class)
      .elementsSubset("identifier", "name") // only include the identifier and name
      .execute();

Create - Type

The following example shows how to perform a create operation using the generic client:

         Patient patient = new Patient();
         // ..populate the patient object..
         patient.addIdentifier().setSystem("urn:system").setValue("12345");
         patient.addName().addFamily("Smith").addGiven("John");

         // Invoke the server create method (and send pretty-printed JSON
         // encoding to the server
         // instead of the default which is non-pretty printed XML)
         MethodOutcome outcome = client.create()
            .resource(patient)
            .prettyPrint()
            .encodedJson()
            .execute();
         
         // The MethodOutcome object will contain information about the
         // response from the server, including the ID of the created 
         // resource, the OperationOutcome response, etc. (assuming that
         // any of these things were provided by the server! They may not
         // always be)
         IdDt id = (IdDt) outcome.getId();
         System.out.println("Got ID: " + id.getValue());

Conditional Creates

FHIR also specifies a type of update called "conditional create", where a set of search parameters are provided and a new resource is only created if no existing resource matches those parameters. See the FHIR specification for more information on conditional creation.

          // One form
          MethodOutcome outcome = client.create()
                  .resource(patient)
                  .conditionalByUrl("Patient?identifier=system%7C00001")
                  .execute();

          // Another form
          MethodOutcome outcome2 = client.create()
                  .resource(patient)
                  .conditional()
                  .where(Patient.IDENTIFIER.exactly().systemAndIdentifier("system", "00001"))
                  .execute();

          // This will return Boolean.TRUE if the server responded with an HTTP 201 created,
          // otherwise it will return null.
          Boolean created = outcome.getCreated();

          // The ID of the created, or the pre-existing resource
          IdDt id = (IdDt) outcome.getId();

Read/VRead - Instance

Given a resource name and ID, it is simple to retrieve the latest version of that resource (a 'read')

// search for patient 123
Patient patient = client.read()
                        .resource(Patient.class)
                        .withId("123")
                        .execute(); 

By adding a version string, it is also possible to retrieve a specific version (a 'vread')

// search for patient 123 (specific version 888)
Patient patient = client.read()
                        .resource(Patient.class)
                        .withIdAndVersion("123", "888")
                        .execute(); 

It is also possible to retrieve a resource given its absolute URL (this will override the base URL set on the client)

// search for patient 123 on example.com
String url = "http://example.com/fhir/Patient/123";
Patient patient = client.read()
                        .resource(Patient.class)
                        .withUrl(url)
                        .execute(); 

See also the page on ETag Support for information on specifying a matching version in the client request.

Delete - Instance

The following example shows how to perform a delete operation using the generic client:

          IBaseOperationOutcome resp = client.delete().resourceById(new IdDt("Patient", "1234")).execute();

         // outcome may be null if the server didn't return one
          if (resp != null) {
              OperationOutcome outcome = (OperationOutcome) resp;
            System.out.println(outcome.getIssueFirstRep().getDetailsElement().getValue());
         }

Conditional Deletes

Conditional deletions are also possible, which is a form where instead of deleting a resource using its logical ID, you specify a set of search criteria and a single resource is deleted if it matches that criteria. Note that this is not a mechanism for bulk deletion; see the FHIR specification for information on conditional deletes and how they are used.

           client.delete()
                   .resourceConditionalByUrl("Patient?identifier=system%7C00001")
                   .execute();

           client.delete()
                   .resourceConditionalByType("Patient")
                   .where(Patient.IDENTIFIER.exactly().systemAndIdentifier("system", "00001"))
                   .execute();

Update - Instance

Updating a resource is similar to creating one, except that an ID must be supplied since you are updating a previously existing resource instance.

The following example shows how to perform an update operation using the generic client:

         Patient patient = new Patient();
         // ..populate the patient object..
         patient.addIdentifier().setSystem("urn:system").setValue("12345");
         patient.addName().addFamily("Smith").addGiven("John");

         // To update a resource, it should have an ID set (if the resource
         // object
         // comes from the results of a previous read or search, it will already
         // have one though)
         patient.setId("Patient/123");

         // Invoke the server update method
         MethodOutcome outcome = client.update()
            .resource(patient)
            .execute();

         // The MethodOutcome object will contain information about the
         // response from the server, including the ID of the created 
         // resource, the OperationOutcome response, etc. (assuming that
         // any of these things were provided by the server! They may not
         // always be)
         IdDt id = (IdDt) outcome.getId();
         System.out.println("Got ID: " + id.getValue());

Conditional Updates

FHIR also specifies a type of update called "conditional updates", where insetad of using the logical ID of a resource to update, a set of search parameters is provided. If a single resource matches that set of parameters, that resource is updated. See the FHIR specification for information on how conditional updates work.

          client.update()
                  .resource(patient)
                  .conditionalByUrl("Patient?identifier=system%7C00001")
                  .execute();

          client.update()
                  .resource(patient)
                  .conditional()
                  .where(Patient.IDENTIFIER.exactly().systemAndIdentifier("system", "00001"))
                  .execute();

ETags and Resource Contention

See also the page on ETag Support for information on specifying a matching version in the client request.

History - Server/Type/Instance

To retrieve the version history of all resources, or all resources of a given type, or of a specific instance of a resource, you call the history() method.

response = client
   .history()
   .onServer()
   .andReturnDstu1Bundle()
   .execute();

If you are using a DSTU2 compliant server, you should instead use the Bundle resource which is found in the DSTU2 structures JAR, as shown in the syntax below. Note that in both cases, the class name is Bundle, but the DSTU2 bundle is found in the .resources. package.

response = client
   .history()
   .onServer()
   .andReturnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class)
   .execute();

You can also optionally request that only resource versions later than a given date, and/or only up to a given count (number) of resource versions be returned.

response = client
   .history()
   .onServer()
   .andReturnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class)
   .since(new InstantDt("2012-01-01T12:22:32.038Z"))
   .count(100)
   .execute();

Transaction - Server

The following example shows how to execute a transaction using the generic client:

         List<IResource> resources = new ArrayList<IResource>();
         // .. populate this list - note that you can also pass in a populated
         // Bundle if you want to create one manually ..

         List<IBaseResource> response = client.transaction().withResources(resources).execute();

Conformance - Server

To retrieve the server's conformance statement, simply call the conformance() method as shown below.

// Retrieve the server's conformance statement and print its
// description
Conformance conf = client.fetchConformance().ofType(Conformance.class).execute();
System.out.println(conf.getDescriptionElement().getValue());

Extended Operations

In the FHIR DSTU2 version, operations (referred to as "extended operations") were added. These operations are an RPC style of invocation, with a set of named input parameters passed to the server and a set of named output parameters returned back.

To invoke an operation using the client, you simply need to create the input Parameters resource, then pass that to the operation() fluent method.

The example below shows a simple operation call.

// Create a client to talk to the HeathIntersections server
FhirContext ctx = FhirContext.forDstu2();
IGenericClient client = ctx.newRestfulGenericClient("http://fhir-dev.healthintersections.com.au/open");
client.registerInterceptor(new LoggingInterceptor(true));

// Create the input parameters to pass to the server
Parameters inParams = new Parameters();
inParams.addParameter().setName("start").setValue(new DateDt("2001-01-01"));
inParams.addParameter().setName("end").setValue(new DateDt("2015-03-01"));

// Invoke $everything on "Patient/1"
Parameters outParams = client
   .operation()
   .onInstance(new IdDt("Patient", "1"))
   .named("$everything")
   .withParameters(inParams)
   .useHttpGet() // Use HTTP GET instead of POST
   .execute();

Note that if the operation does not require any input parameters, you may also invoke the operation using the following form. Note that the withNoParameters still requires you to provide the type of the Parameters resource so that it can return the correct type in the response.

// Create a client to talk to the HeathIntersections server
FhirContext ctx = FhirContext.forDstu2();
IGenericClient client = ctx.newRestfulGenericClient("http://fhir-dev.healthintersections.com.au/open");
client.registerInterceptor(new LoggingInterceptor(true));

// Invoke $everything on "Patient/1"
Parameters outParams = client
   .operation()
   .onInstance(new IdDt("Patient", "1"))
   .named("$everything")
   .withNoParameters(Parameters.class) // No input parameters
   .execute();

Using the HTTP GET Form

By default, the client will invoke operations using the HTTP POST form. The FHIR specification also allows requests to use the HTTP GET verb if the operation is idempotent and has no composite/resource parameters. Use the following form to invoke operation with HTTP GET.

// Create a client to talk to the HeathIntersections server
FhirContext ctx = FhirContext.forDstu2();
IGenericClient client = ctx.newRestfulGenericClient("http://fhir-dev.healthintersections.com.au/open");
client.registerInterceptor(new LoggingInterceptor(true));

// Create the input parameters to pass to the server
Parameters inParams = new Parameters();
inParams.addParameter().setName("start").setValue(new DateDt("2001-01-01"));
inParams.addParameter().setName("end").setValue(new DateDt("2015-03-01"));

// Invoke $everything on "Patient/1"
Parameters outParams = client
   .operation()
   .onInstance(new IdDt("Patient", "1"))
   .named("$everything")
   .withParameters(inParams)
   .useHttpGet() // Use HTTP GET instead of POST
   .execute();

Built-In Operations - Validate

The $validate operation asks the server to test a given resource to see if it would be acceptable as a create/update on that server. The client has built-in support for this operation.

If the client is in DSTU1 mode, the method below will invoke the DSTU1 validation style instead.

Patient patient = new Patient();
patient.addIdentifier().setSystem("http://hospital.com").setValue("123445");
patient.addName().addFamily("Smith").addGiven("John");

// Validate the resource
MethodOutcome outcome = client.validate()
   .resource(patient)
   .execute();

// The returned object will contain an operation outcome resource
OperationOutcome oo = (OperationOutcome) outcome.getOperationOutcome();

// If the OperationOutcome has any issues with a severity of ERROR or SEVERE,
// the validation failed.
for (Issue nextIssue : oo.getIssue()) {
   if (nextIssue.getSeverityElement().getValueAsEnum().ordinal() >= IssueSeverityEnum.ERROR.ordinal()) {
      System.out.println("We failed validation!");
   }
}

Back to top

Reflow Maven skin by Andrius Velykis.