Jam EMR is the only open source clinical system that stores patient data natively as FHIR R4 — not translated, not wrapped, not exported. 16 clinical modules, SMART on FHIR v2, DHIS2 national reporting, and a complete insurance revenue cycle. Runs anywhere with one Docker command.
Every other open source EMR stores clinical data in a proprietary schema and adds a FHIR facade on top. Jam EMR uses HAPI FHIR JPA as its only database — giving you a fully interoperable system from day one, with zero translation overhead.
Patients are Patient resources. Prescriptions are MedicationRequest resources. Invoices are Invoice resources. Query the standard FHIR API directly at http://localhost:8090/fhir — no custom endpoints, no wrappers, no second database to manage. Any FHIR-capable system integrates from day one.
Patient and Encounter to Coverage, Claim, ClaimResponse, and AuditEvent — the entire clinical and financial workflow expressed in standard FHIR. Any FHIR-capable system integrates directly. No custom adapter work, ever.allowedDependencies verified by ModularStructureTest on every Maven build. Cycle-free. Feature-flagged with @ConditionalOnProperty — turn off what you don't need for zero startup cost.VitalsSavedEvent triggers NEWS2, MedicationSavedEvent triggers drug-allergy, CriticalResultEvent surfaces alerts. Each is a typed Java record — impossible to send the wrong payload.CompletableFuture, cached for 60 seconds with a 200-entry maximum. The dashboard is always fast — even on large datasets with thousands of patients and encounters.DocumentReference resources with LOINC codes 57833-6 and 60591-5. Every printable document is also a queryable FHIR resource.Questionnaire + QuestionnaireResponse with group nesting, conditional logic, LOINC codes per item, and all FHIR item types. No Java required.From a single-doctor outpatient clinic to a 200-bed district hospital reporting to a national HMIS — Jam EMR scales with your organisation and grows with your needs.
Feature flags via @ConditionalOnProperty mean unused modules never start. Configure once in Docker Compose; override any flag per environment with a single environment variable.
/queue/display/ — no auth required, designed for lobby screens. Lifecycle: proposed → booked → arrived → fulfilled → cancelled.Cache-Control: no-store on all /std/** responses, ICD-10 A50–A64 hidden from general lists.Jam EMR's clinical workflow is a continuous chain of FHIR resources. Every action creates or updates a resource that is immediately queryable, auditable, and interoperable.
Patient resource in HAPI FHIR JPA with demographics, MRN as identifier, blood group and emergency contacts as extensions. PatientCreatedEvent fires. The patient is instantly searchable by name, MRN, phone, or any FHIR search parameter.Appointment resource is created and a queue token assigned. Check-in sets status to arrived. The practitioner calls the patient — calledAt recorded. The TV display at /queue/display/ updates in real time — no auth required, designed for lobby screens.Encounter (class: AMB / IMP / EMER). All clinical actions — vitals, conditions, prescriptions, investigations — reference this encounter. EncounterCreatedEvent fires. The encounter timeline is the canonical record for the visit.Observation resources with standard LOINC codes. VitalsSavedEvent fires asynchronously. News2Calculator computes the NEWS2 score (0–20) and stores a RiskAssessment. Elevated scores log a CDS audit event. The practitioner sees the score on next page load.MedicationRequest is created. MedicationSavedEvent fires. DrugAllergyChecker runs three-level matching: exact SNOMED → display name overlap → drug class cross-reactivity. A DetectedIssue is stored and shown as a banner on the chart if a conflict is found.Invoice with ChargeItemDefinition line items. A Claim is generated with pre-tax item[].net values. The insurer's remittance creates a ClaimResponse. reconcileInvoice() updates patientOwes and marks the invoice balanced. Gap invoicing keeps the invoice open if the patient owes a remainder.NationalIndicatorService aggregates OPD, malaria (B50–B54), TB, EPI doses, and PEPFAR HIV indicators. Dhis2ExportService POSTs a dataValueSet JSON to your DHIS2 instance, or produces a CSV for manual upload.Every design decision in Jam EMR has a documented reason. Here are the decisions that matter most in a clinical system.
reporting schema via @Qualifier("reportingTx")package-info.java declaring allowedDependenciesModularStructureTest.verifiesModularStructure() fails the build on any violationinternal/ packages — inaccessible cross-module@ApplicationModuleListener on CDS and contact-tracing listeners@ConditionalOnProperty on every feature-flagged module's beansCoverage.payor[] → Organization (insurer) — no separate insurer entityClaim.item[].net = pre-tax subtotal (insurers don't pay VAT/GST)ClaimResponseService.reconcileInvoice() is idempotent — safe to call twiceissued for cash collectionCoverage.class[] uses standard coverage-class CodeSystem for plan/group@ControllerAdvice strips HAPI URLs, JDBC strings, stack traces from browserhealth and info exposed — env never accessibleCriticalResultEvent, VitalsSavedEvent (triggers NEWS2), MedicationSavedEvent (triggers drug-allergy), StdDiagnosisConfirmedEvent (triggers contact-tracing), PregnancyRegisteredEvent, EncounterCreatedEvent, PatientCreatedEvent. Each is a typed Java record — no stringly-typed payloads, no runtime surprises.
Every dependency is a deliberate choice — maintained by large organisations, widely deployed in healthcare, and stable enough to build a clinical system on.
Jam EMR's CDS layer is embedded in the prescription and vitals workflow — not bolted on as an afterthought. Two systems fire automatically with zero configuration required.
@Async("taskExecutor"). Audit writes never block the HTTP request thread. If HAPI is slow, clinical operations complete normally.Docker Compose launches HAPI FHIR JPA, Orthanc PACS, OHIF Viewer 3, and the Jam EMR app — all networked, all configured, all ready. No FHIR expertise required to get started.
Jam EMR implements the complete SMART App Launch Framework v2.0. Not a subset, not "mostly compliant." Every PKCE parameter, every RS256 claim, every token-refresh rule — implemented to spec.
GET /smart/launch?iss=&launch=. The EMR validates ISS against the SMART discovery document (cached per-ISS), builds a PKCE pair with a 96-byte cryptographically random verifier and S256 challenge, then redirects to the EHR authorize endpoint with a state nonce for CSRF protection.DefaultJWTProcessor with RemoteJWKSet against the EHR's JWKS endpoint. Signature, expiry, issuer, audience, and nonce all verified. Full RFC 7517 key rotation support — no key pinning that breaks on EHR certificate renewals.SmartSecurityFilter populates the Spring SecurityContext on every request. TokenRefreshFilter proactively refreshes tokens on /api/** routes 120 seconds before expiry — no expired-token errors surfacing to clinicians mid-workflow.Clinical systems handle the most sensitive personal data that exists. Every security control in Jam EMR is deliberate — because "it probably works" is not acceptable in healthcare.
applySecurityHeaders() in every security mode.max-age=31536000, includeSubDomains. Only activated with emr.security.mode=smart — skipped in development so plain-HTTP local setups work normally.@ControllerAdvice catches every unhandled exception. HAPI URLs, JDBC connection strings, and internal paths are stripped by sanitise() before any message reaches the browser.ROLE_HIV_PROVIDER) and STD (ROLE_STD_PROVIDER) enforcement on service methods — protection holds in every security mode including development./std/** responses set Cache-Control: no-store, Pragma: no-cache. ICD-10 A50–A64 filtered from general condition lists.POST /patients/deactivate/{id} sets Patient.active=false (CSRF-protected, preserves full record history). The GET delete URL redirects with an error — no link-based or crawled data loss.ConcurrentMapCacheManager which had no eviction. Bounded cache prevents unbounded memory growth on high-traffic deployments with thousands of daily dashboard loads./actuator/health (Docker/K8s probe) and /actuator/info only. /actuator/env, /actuator/beans, and all introspection endpoints are never accessible.Configure your country's DHIS2 UID mappings once and the reporting module handles the rest. PEPFAR/DATIM UIDs are pre-configured across all deployments.
Jam EMR leads in three areas no other open source EMR addresses together: native FHIR storage, automatic CDS that fires without configuration, and a complete insurance revenue cycle expressed entirely in FHIR resources.
| Feature | Jam EMR | OpenMRS 3.x | Bahmni | GNU Health | OSCAR EMR |
|---|---|---|---|---|---|
| FHIR R4 as native storage | ✓ Native HAPI JPA | Adapter | ✗ | ✗ | ✗ |
| SMART App Launch v2.0 (PKCE + RS256) | ✓ Full v2.0 | Partial | Partial | ✗ | ✗ |
| Drug-allergy CDS at prescribing (auto) | ✓ 3-level match | ✗ | Pharmacy only | ✗ | ✗ |
| NEWS2 early warning score (automatic) | ✓ Every vitals save | ✗ | ✗ | ✗ | ✗ |
| FHIR insurance claim lifecycle (3 phases) | ✓ Coverage → Claim → ClaimResponse | ✗ | Non-FHIR | Partial | ✓ (non-FHIR) |
| DHIS2 / DATIM national reporting | ✓ Built-in | Module | Module | Export only | ✗ |
| STD privacy stack (FHIR security labels) | ✓ Labels + no-store headers | ✗ | ✗ | ✗ | ✗ |
| WHO growth charts + z-scores | ✓ WAZ / HAZ / WHZ | Module | ✓ | Partial | ✗ |
| Orthanc PACS + OHIF Viewer 3 | ✓ In Docker Compose | Module | ✓ dcm4chee | Partial | Partial |
| Spring Modulith architecture (build-time) | ✓ Verified every build | ✗ | ✗ | ✗ | ✗ |
| Single command deploy (docker compose up) | ✓ | Partial | ✓ | Partial | ✗ |
| Open source licence | MIT | MPL 2.0 | LGPL / MPL | GPL 3.0 | GPL 2.0 |
http://localhost:8090/fhir with standard FHIR search parameters from day one. No custom adapter work, ever.Coverage (insurer, policy number, copay/deductible). Phase 2: Generate a Claim from the issued Invoice — pre-tax line items map to Claim.item[], 5 claim-tracking extensions are written back to the Invoice so billing and insurance modules stay independent. Phase 3: When the insurer's Remittance Advice arrives, record a ClaimResponse. Click "Reconcile Invoice" to set insuranceCovered, update patientOwes, and mark the invoice balanced. If a gap remains, the invoice stays issued so billing can collect the patient's portion normally.DHIS2_URL, DHIS2_USERNAME, DHIS2_PASSWORD, and DHIS2_ORG_UNIT (your facility's org unit UID). Then one property per indicator mapping your data element UIDs. PEPFAR DATIM UIDs (TX_CURR, TX_NEW, HTS_TST, HTS_POS) are pre-configured and don't vary by country. In download-only mode you still get a JSON dataValueSet and HMIS CSV for manual upload.com.emr.fhir.yourmodule, add a package-info.java with @ApplicationModule(allowedDependencies = {"core"}), annotate your service with @ConditionalOnProperty, and add the flag to Docker Compose. The Modulith architecture test verifies your boundary declarations on every build. Subscribe to existing clinical events without coupling to any other module's internal classes.Community support is free and permanent. Structured support is available for clinics and governments that need deployment guarantees.