Skip to content

FHIR Integration

Deep dive into HL7 FHIR R4 implementation.

HAPI FHIR Server

Configuration

@Configuration
public class FhirConfig {

    @Bean
    public FhirContext fhirContext() {
        return FhirContext.forR4();
    }

    @Bean
    public IGenericClient fhirClient(FhirContext fhirContext) {
        String serverBase = "http://localhost:8080/api/fhir";
        return fhirContext.newRestfulGenericClient(serverBase);
    }
}

13 Resources Implemented

  1. Patient - Demographics
  2. Practitioner - Providers
  3. Organization - Facilities
  4. Appointment - Scheduling
  5. Encounter - Visits
  6. Condition - Diagnoses
  7. MedicationRequest - Prescriptions
  8. AllergyIntolerance - Allergies
  9. Procedure - Procedures
  10. ServiceRequest - Orders
  11. DocumentReference - Documents
  12. FamilyMemberHistory - Family history
  13. AuditEvent - Audit trail

Resource Mappings

Patient Resource

FHIR → DTO:

public PatientDTO toDTO(Patient fhir) {
    PatientDTO dto = new PatientDTO();
    dto.setId(fhir.getIdElement().getIdPart());
    dto.setFirstName(fhir.getNameFirstRep().getGivenAsSingleString());
    dto.setLastName(fhir.getNameFirstRep().getFamily());
    dto.setGender(fhir.getGender().toCode());
    dto.setBirthDate(formatDate(fhir.getBirthDate()));
    // Extract identifiers
    for (Identifier id : fhir.getIdentifier()) {
        if ("MRN".equals(id.getSystem())) {
            dto.setMrn(id.getValue());
        }
    }
    return dto;
}

DTO → FHIR:

public Patient toFHIR(PatientDTO dto) {
    Patient fhir = new Patient();
    fhir.setId(dto.getId());

    HumanName name = new HumanName();
    name.setFamily(dto.getLastName());
    name.addGiven(dto.getFirstName());
    fhir.addName(name);

    fhir.setGender(Enumerations.AdministrativeGender.fromCode(dto.getGender()));
    fhir.setBirthDate(parseDate(dto.getBirthDate()));

    Identifier mrn = new Identifier();
    mrn.setSystem("MRN");
    mrn.setValue(dto.getMrn());
    fhir.addIdentifier(mrn);

    return fhir;
}

Condition Resource with ICD-10

public Condition createCondition(ConditionDTO dto) {
    Condition condition = new Condition();

    // Patient reference
    condition.setSubject(new Reference("Patient/" + dto.getPatientId()));

    // ICD-10 code
    CodeableConcept code = new CodeableConcept();
    Coding coding = new Coding();
    coding.setSystem("http://hl7.org/fhir/sid/icd-10-cm");
    coding.setCode(dto.getCode());           // e.g., "E11.9"
    coding.setDisplay(dto.getDisplay());     // e.g., "Type 2 diabetes"
    code.addCoding(coding);
    condition.setCode(code);

    // Clinical status
    CodeableConcept clinicalStatus = new CodeableConcept();
    clinicalStatus.addCoding()
        .setSystem("http://terminology.hl7.org/CodeSystem/condition-clinical")
        .setCode(dto.getClinicalStatus());
    condition.setClinicalStatus(clinicalStatus);

    return condition;
}

MedicationRequest with SNOMED

public MedicationRequest createPrescription(PrescriptionDTO dto) {
    MedicationRequest rx = new MedicationRequest();

    // Patient reference
    rx.setSubject(new Reference("Patient/" + dto.getPatientId()));

    // SNOMED medication code
    CodeableConcept medication = new CodeableConcept();
    Coding coding = new Coding();
    coding.setSystem("http://snomed.info/sct");
    coding.setCode(dto.getMedicationCode());  // e.g., "108774000"
    coding.setDisplay(dto.getMedicationDisplay()); // e.g., "Metformin"
    medication.addCoding(coding);
    rx.setMedication(medication);

    // Dosage
    Dosage dosage = new Dosage();
    dosage.setText(dto.getInstructions());
    dosage.setRoute(createRoute(dto.getRoute()));
    dosage.setTiming(createTiming(dto.getFrequency()));
    rx.addDosageInstruction(dosage);

    return rx;
}

CRUD Operations

Create

@Service
public class ConditionService {

    @Autowired
    private IGenericClient fhirClient;

    public String create(ConditionDTO dto) {
        Condition condition = convertToFHIR(dto);

        MethodOutcome outcome = fhirClient.create()
            .resource(condition)
            .execute();

        return outcome.getId().getIdPart();
    }
}

Read

public ConditionDTO read(String id) {
    Condition condition = fhirClient.read()
        .resource(Condition.class)
        .withId(id)
        .execute();

    return convertToDTO(condition);
}

Update

public void update(String id, ConditionDTO dto) {
    Condition condition = convertToFHIR(dto);
    condition.setId(id);

    fhirClient.update()
        .resource(condition)
        .execute();
}

Delete

public void delete(String id) {
    fhirClient.delete()
        .resourceById("Condition", id)
        .execute();
}
public List<ConditionDTO> searchByPatient(String patientId) {
    Bundle bundle = fhirClient.search()
        .forResource(Condition.class)
        .where(Condition.SUBJECT.hasId(patientId))
        .returnBundle(Bundle.class)
        .execute();

    return bundle.getEntry().stream()
        .map(entry -> (Condition) entry.getResource())
        .map(this::convertToDTO)
        .collect(Collectors.toList());
}

Validation

Resource Validation

@Bean
public IValidatorModule validatorModule(FhirContext ctx) {
    FhirInstanceValidator instanceValidator = new FhirInstanceValidator(ctx);
    instanceValidator.setValidationSupport(new DefaultProfileValidationSupport(ctx));
    return instanceValidator;
}

Custom Validation

public void validateCondition(Condition condition) {
    // Ensure ICD-10 code format
    String code = condition.getCode().getCodingFirstRep().getCode();
    if (!code.matches("[A-Z]\\d{2}(\\.\\d+)?")) {
        throw new InvalidRequestException("Invalid ICD-10 code format");
    }
}

Search Parameters

GET /api/fhir/Patient?name=John
GET /api/fhir/Patient?birthdate=1980-01-15
GET /api/fhir/Patient?identifier=MRN-12345
GET /api/fhir/Patient?_count=50
GET /api/fhir/Condition?patient=123
GET /api/fhir/Condition?code=E11.9
GET /api/fhir/Condition?clinical-status=active

Next Steps