FHIR Code in 10 Minutes
This is a simple code to get started on FHIR with Java. Using HAPI FHIR library with an example of Patient Resource.
Join the DZone community and get the full member experience.
Join For FreeYou have read tons of articles on FHIR and the tell-tale signs have appeared on hairs on the back of your neck. Your gut tells you to get your feet wet (you got to admit that is funny, gut talking to feet) and ease out that angst. You may be an Architect (hands-on or not) or hands-on Engineer or may be rusty on coding or even deep into it, you could be coming from any such background but FHIR is the beast that you are looking to wrestle. If this speaks to you then let's work together for 10 minutes and then you can go back to the world of FHIR with a renewed goal like Robert the Bruce from the famous spider legend.
This code is for FHIR Facade pattern meaning that we are not going for full FHIR Restful Server implementation and our objective here is to only retrieve data. We will use Spring Boot just because those Booties are so awesome and simple. We will also use our friendly HAPI FHIR library to create our java application.
Tools: Postman, IntelliJ 2019.1 that's it.. ahem.. Java 8 installed on your machine. Everything else like HAPI, we will grab 'em as we go here.
Pre-requisite: None. It's assumed that you have an interest in FHIR and understand Java. Silly me, if not why would you read this far?
Take-Home: When you are done here, you will be able to create an FHIR Restful server and be able to search for patients based on the family name.
Enough prologue, I know times ticking so let's start the clock here.
1. In your first minute, we will set up your Spring Boot project in IntelliJ.
Load up your IntelliJ IDE and click on "New Project".
Select Spring Initializr on the left-hand side.
On the right side pane Let’s select the
Project SDK 1.8 and leave
“Default” for Choose Intializr Server URL.
Click on Next
In the next step enter as below.
Group: com.example
Artifact: fhirexample
Type: Maven Project
Language: Java
Packaging: Jar
Java Version: 8
Version: 0.0.1-SNAPSHOT
Name: FHIR Example
Description: My first FHIR project
Package: com.example.fhirexample
Then click on Next
In this step from Developer Tools
Select
Spring Boot DevTools
Lombok (Optional, but it will come in handy when you expand on this example)
From Web Select
Spring Web
From SQL Select
H2 Database
Click Next
In this step enter a project name and location
Project Name: fhirexample
Project Location: c:\fhirexample (make sure this directory exists)
Click on Finish. Now your project is created and ready to dance on your music.
2. This is going to be our HAPI minute
Your Spring is ready but you are not quite FHIR ready yet. So In your second minute we will load up HAPI library. While there are many subprojects in FHIR Codebase we only need 3 for FHIR Façade or as James Agnew puts it, for HAPI Plain Server implementation.
Hapi-fhir-base
Hapi-fhir-structures
Hapi-fhir-jpaserver.base
So let's go ahead and add these dependencies in our project. You can follow through screenshots here.
Click on File->Project Structures.
Under Project, Settings click on Libraries
On the right-hand pane click on the “+” sign and select “From Maven”.
Then enter these libraries one after other
ca.uhn.hapi.fhir:hapi-fhir-base:5.0.2
After you click Ok IntelliJ will ask you this
Just click Ok.
Note: This dialogue will reappear whenever you add a new library.
ca.uhn.hapi.fhir:hapi-fhir-structures-r5:5.0.1
hapi-fhir-jpaserver-base:5.0.2
NOTE: If by any chance you had forgotten to select H2 Database while setting up Spring boot this will be the time to rectify that mistake by selecting
com.h2database:h2:1.4.200 from maven library just like the steps above.
Click Ok on this screen to close this window.
There We are in Minute 2.
Minute 3 relax. Never mind, you want to get to the finish line before its too late, and research says that the average attention span of the modern human is between 8 to 12 seconds. That means we are now competing with goldfish which has a span of 9 seconds. And it also means that I am already at the borrowed time, so let's get back on our horse and get this baby running.
3. This minute we will spend reviewing our current state. If you expand on your project and go down to fhirexample->src->main->java->com.example.fhirexample->fhirExampleApplication
Your screen looks would look like this with the main class created inside your IntelliJ. This would be a good time to check on .iml and pom file as well.
Note that we added all the dependency to .iml file and not pom. The difference is out of the scope of this discussion. But something for you to mull over.
The first code snippet is pom.xml and next one is for fhirexample.iml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>fhirexample</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>FHIR Example</name>
<description>My first FHIR project</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
xxxxxxxxxx
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="Spring" name="Spring">
<configuration />
</facet>
<facet type="web" name="Web">
<configuration>
<webroots />
<sourceRoots>
<root url="file://$MODULE_DIR$/src/main/java" />
<root url="file://$MODULE_DIR$/src/main/resources" />
</sourceRoots>
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">
<output url="file://$MODULE_DIR$/target/classes" />
<output-test url="file://$MODULE_DIR$/target/test-classes" />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-web:2.3.1.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter:2.3.1.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-logging:2.3.1.RELEASE" level="project" />
<orderEntry type="library" name="Maven: ch.qos.logback:logback-classic:1.2.3" level="project" />
<orderEntry type="library" name="Maven: ch.qos.logback:logback-core:1.2.3" level="project" />
<orderEntry type="library" name="Maven: org.apache.logging.log4j:log4j-to-slf4j:2.13.3" level="project" />
<orderEntry type="library" name="Maven: org.apache.logging.log4j:log4j-api:2.13.3" level="project" />
<orderEntry type="library" name="Maven: org.slf4j:jul-to-slf4j:1.7.30" level="project" />
<orderEntry type="library" name="Maven: jakarta.annotation:jakarta.annotation-api:1.3.5" level="project" />
<orderEntry type="library" name="Maven: org.yaml:snakeyaml:1.26" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-json:2.3.1.RELEASE" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-databind:2.11.0" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-annotations:2.11.0" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-core:2.11.0" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.11.0" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.11.0" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.module:jackson-module-parameter-names:2.11.0" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-tomcat:2.3.1.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.apache.tomcat.embed:tomcat-embed-core:9.0.36" level="project" />
<orderEntry type="library" name="Maven: org.glassfish:jakarta.el:3.0.3" level="project" />
<orderEntry type="library" name="Maven: org.apache.tomcat.embed:tomcat-embed-websocket:9.0.36" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-web:5.2.7.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-beans:5.2.7.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-webmvc:5.2.7.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-aop:5.2.7.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-context:5.2.7.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-expression:5.2.7.RELEASE" level="project" />
<orderEntry type="library" scope="RUNTIME" name="Maven: org.springframework.boot:spring-boot-devtools:2.3.1.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot:2.3.1.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-autoconfigure:2.3.1.RELEASE" level="project" />
<orderEntry type="library" scope="RUNTIME" name="Maven: com.h2database:h2:1.4.200" level="project" />
<orderEntry type="library" name="Maven: org.projectlombok:lombok:1.18.12" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.springframework.boot:spring-boot-starter-test:2.3.1.RELEASE" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.springframework.boot:spring-boot-test:2.3.1.RELEASE" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.springframework.boot:spring-boot-test-autoconfigure:2.3.1.RELEASE" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: com.jayway.jsonpath:json-path:2.4.0" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: net.minidev:json-smart:2.3" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: net.minidev:accessors-smart:1.2" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.ow2.asm:asm:5.0.4" level="project" />
<orderEntry type="library" name="Maven: org.slf4j:slf4j-api:1.7.30" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: jakarta.xml.bind:jakarta.xml.bind-api:2.3.3" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: jakarta.activation:jakarta.activation-api:1.2.2" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.assertj:assertj-core:3.16.1" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.hamcrest:hamcrest:2.2" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.junit.jupiter:junit-jupiter:5.6.2" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.junit.jupiter:junit-jupiter-api:5.6.2" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.apiguardian:apiguardian-api:1.1.0" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.opentest4j:opentest4j:1.2.0" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.junit.platform:junit-platform-commons:1.6.2" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.junit.jupiter:junit-jupiter-params:5.6.2" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.junit.jupiter:junit-jupiter-engine:5.6.2" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.junit.platform:junit-platform-engine:1.6.2" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.mockito:mockito-core:3.3.3" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: net.bytebuddy:byte-buddy:1.10.11" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: net.bytebuddy:byte-buddy-agent:1.10.11" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.objenesis:objenesis:2.6" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.mockito:mockito-junit-jupiter:3.3.3" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.skyscreamer:jsonassert:1.5.0" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: com.vaadin.external.google:android-json:0.0.20131108.vaadin1" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-core:5.2.7.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-jcl:5.2.7.RELEASE" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.springframework:spring-test:5.2.7.RELEASE" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.xmlunit:xmlunit-core:2.7.0" level="project" />
<orderEntry type="library" name="ca.uhn.hapi.fhir:hapi-fhir-base:5.0.2" level="project" />
<orderEntry type="library" name="ca.uhn.hapi.fhir:hapi-fhir-structures-r5:5.0.1" level="project" />
<orderEntry type="library" name="ca.uhn.hapi.fhir:hapi-fhir-jpaserver-base:5.0.2" level="project" />
</component>
</module>
4. Now we will start to code for FHIR.
Right-click on your package com.example.fhirexample and select New Class.
Enter Name: SimpleRestfulServer
Kind: Class
Click on OK.
Copy this code into your class and pay attention to the Initialize method. What we are doing is setting FHIR Context (this is going to be expensive so set it once Read more about it ... Just not in while our clock is ticking :) ). This class is going to be the heart of your FHIR code and initialize method is going to be driving everything in your heart. Don’t drive her crazy though.
x
package com.example.fhirexample;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.server.RestfulServer;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
"/*") (
public class SimpleRestfulServer extends RestfulServer{
//Initialize
protected void initialize()throws ServletException{
//create a context for the appropriate version
setFhirContext(FhirContext.forR5());
//Register Resource Providers - COMING SOON
}
}
5. Now lets Setup Servlet Context. This is Spring Boot ABC. Open your FhirExampleApplication Class and copy the code below. Pay attention to the Bean Annotation. This is where we are bringing FHIR into play.
xxxxxxxxxx
package com.example.fhirexample;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
public class FhirExampleApplication {
public static void main(String[] args) {
SpringApplication.run(FhirExampleApplication.class, args);
}
public ServletRegistrationBean ServletRegistrationBean(){
ServletRegistrationBean registration=new ServletRegistrationBean(new SimpleRestfulServer(),"/*");
registration.setName("FhirServlet");
return registration;
}
}
6. You are now ready to see results of your effort thus far and mark your first step into FHIR. So go ahead and click on Run FhirExampleApplication(From Run menu or button on top right or just press Shift+F10). Once your server has started, Open up your Postman and enter this URL http://localhost:8080/metadata. A capability statement will appear just like the screenshot below. Congratulations you are now FHIR enabled!
(True Fact: By mere publishing, a capability statement technically speaking you are FHIR capable now. You don’t have any resources exposed but you can claim that status.)
I have observed that the Server ate away 7-10 seconds of our time just to start. Hopefully, we will make up time somewhere by the end here.
7. Now its time to add a Patient resource and add meaning to your existence as an FHIR capable server. So right-click on your package com.example.fhirexample and from the context menu select
New-> Package. Enter package name as ResourceProvider, just so that you are organized and whole all your Resource class in one package.
This step would buy us back the 7 seconds we lost in our last step. But hey who is keeping count, right?
8. Inside Resource, package creates a class PatientResourceProvider (Right-click on ResourceProvider, then New->Class). copy this code into it.
xxxxxxxxxx
package com.example.fhirexample.ResourceProvider;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import org.hl7.fhir.dstu2.model.IdType;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r5.model.Patient;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class PatientResourceProvider implements IResourceProvider {
/**
* Constructor
*/
public PatientResourceProvider() { }
//A Map to hold all the patients.
private Map<String, Patient> patientMap = new HashMap<String, Patient>();
/* You want to be a law abiding class so please
Implement abstract method getResourceProvider() from IResourceProvider
*/
public Class<? extends IBaseResource> getResourceType() {
return Patient.class;
}
//Create a Map to hold patients data
/**
* The "@Search" annotation indicates that this method supports the
* search operation. Similarly we can have more Search annotations defined
* they can each take different parameters
* Documentation just gets added to your Capability Statement
*
* @param theFamilyName
* This operation takes one parameter which is the search criteria. It is
* annotated with the "@Required" annotation. This annotation takes one argument,
* a string containing the name of the search criteria. The datatype here
* is StringParam, but there are other possible parameter types depending on the
* specific search criteria.
* There is a "@Optional" tag which can be used if you want to have a search criteria as optional
* @return
* This method returns a list of Patients. This list may contain multiple
* matching resources, or it may also be empty.
* This annotation takes a "name" parameter which specifies the parameter's name (as it will appear in the search URL).
* FHIR defines standardized parameter names for each resource, and these are available as constants on the individual
* HAPI resource classes.
*/
public List<Patient> search( (name = Patient.SP_FAMILY) StringParam theParam) {
List<Patient> patients = new ArrayList<Patient>();
patients = this.searchByFamilyName(theParam.getValue());
return patients;
}
/**
* Simple implementation of the "read" method
* The "@Read" annotation indicates that this method supports the
* * read operation. Read operations should return a single resource
* * instance.
*
* This method will support a query like this http://localhost:8080/Patient/1
*/
()
public Patient read( IdType theId) {
loadDummyPatients();
Patient retVal = patientMap.get(theId.getIdPart());
if (retVal == null) {
throw new ResourceNotFoundException(theId);
}
return retVal;
}
private List<Patient> searchByFamilyName(String familyName){
List<Patient> retPatients;
loadDummyPatients();
// Encode the output, including the narrative - see below
// Loop through the patients looking for matches
retPatients = patientMap.values()
.stream()
.filter(next -> familyName.toLowerCase().equals(next.getNameFirstRep().getFamily().toLowerCase()))
.collect(Collectors.toList());
return retPatients;
}
private void loadDummyPatients() {
Patient patient = new Patient();
patient.setId("1");
patient.addIdentifier().setSystem("http://optum.com/MRNs").setValue("007");
patient.addName().setFamily("Chakravarty").addGiven("Mithun").addGiven("A");
patient.addAddress().addLine("Address Line 1");
patient.addAddress().setCity("Mumbai");
patient.addAddress().setCountry("India");
patient.addTelecom().setValue("111-111-1111");
this.patientMap.put("1", patient);
for (int i = 2; i < 5; i++) {
patient = new Patient();
patient.setId(Integer.toString(i));
patient.addIdentifier().setSystem("http://optum.com/MRNs").setValue("007" + i);
patient.addName().setFamily("Bond" + i).addGiven("James").addGiven("J");
patient.addAddress().addLine("House Line " + i);
patient.addAddress().setCity("Your City");
patient.addAddress().setCountry("USA");
this.patientMap.put(Integer.toString(i), patient);
}
}
}
- Now its time to activate this Resource Provider so go back to SimpleRestfulServer class and add this line to the end of the initialize method - You know where we have "Coming Soon" comment. It has now come!
registerProvider(new PatientResourceProvider());
Here is the whole code snipped just in case if you want to copy and paste the whole SimpleRestfulServer class.
xxxxxxxxxx
package com.example.fhirexample;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.server.RestfulServer;
import com.example.fhirexample.ResourceProvider.PatientResourceProvider;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
"/*") (
public class SimpleRestfulServer extends RestfulServer{
//Initialize
protected void initialize()throws ServletException{
//createacontextfortheappropriateversion
setFhirContext(FhirContext.forR5());
//Registerresourceproviders
registerProvider(new PatientResourceProvider());
}
}
Now its time to see your final results. Go ahead and click on Restart FhirExampleApplication. Once your server has started, open up your Postman and
Enter URL
http://localhost:8080/Patient?family=Chakravarty – This will return search results from our list for patients with last name Chakravarty. PS: One of the greatest actors of our time. Read more about Mithun Chakravarty.
That's it!
But wait there is more A little bonus for you.
Enter this URL
http://localhost:8080/Patient/1 -
Here you are reading the first patient on your list. This has been enabled because we added a @Read annotation in our PatientResourceProvider.
I am excluding screenshot here because that is going to be your badge of honor, please share those!
10. Congratulations you have reached the end with one minute to spare. Don’t take it back yet. Please spend this one minute in leaving a comment, suggestion, feedback thought, question, just about anything... EXCEPT for SILENCE.
Conclusion
FHIR standard is extensive and is in very early stages. There is a lot of energy and enthusiasm behind making this happen. It can be overwhelming for anyone. Sometimes you have to try it once as the first step towards comfort.
Even if you have never written a single line of code before, I feel this post will still help you set up with that first step. I hope you feel empowered now to go back out there and conquer the FHIR world. There are many concepts here that need further explanation. I will be more than happy to address those in Q and A.
Opinions expressed by DZone contributors are their own.
Comments