Let me be straight with you. If you are running OpenHIM in production right now, you are probably doing fine. It works. The OpenHIE community is solid, the documentation is reasonable, and the mediator ecosystem gives you a lot for free. I am not writing this to trash-talk a project that has genuinely helped health interoperability in some very difficult operating environments.
But if you are evaluating OpenHIM — whether to build a new national health information exchange, to replace an aging gateway deployment, or to support a SMART on FHIR app ecosystem — then you need to understand some things the README does not tell you. And you need to know that there is now a serious alternative.
This post is the outcome of a complete ground-up reimplementation of the OpenHIM feature set in Java 17 + Spring Boot 3.3 + Apache Camel 4.8 + HAPI FHIR 7.4. The result is JamGateway — a single deployable JAR that replaces five OpenHIM services, achieves ~99% feature parity, and adds several capabilities OpenHIM simply does not have.
CTOs and engineering directors evaluating HIE gateway technology. Lead architects designing national-scale health platforms. Senior engineers maintaining OpenHIM in production who are wondering whether the upgrade path is getting harder or easier. And anyone who has ever received a 3am alert that the MongoDB replica set is down and the entire HIE is offline.
The Problem Nobody Talks About in the OpenHIM Docs
OpenHIM was designed in 2012-2014. At that time, Node.js was the exciting new thing, MongoDB was what you reached for when you needed flexibility, and the concept of a “microservice” that ran independently and communicated via HTTP was genuinely novel in the healthcare space. The architecture reflected the best thinking of its era.
The problem is that era is now over a decade old, and the accumulation of decisions made in that context shows in the codebase. Not as a failure — as a natural consequence of software aging in a world that moves forward.
Here is what a realistic OpenHIM deployment looks like when you actually need to connect a hospital using HL7v2, route to a FHIR R4 server, and maintain an IHE-compliant audit trail:
You run openhim-core-js for the gateway itself — Node.js, backed by MongoDB. You run openhim-console separately — an Angular SPA that communicates with the core via API. You run the mapping mediator — another Node.js service, with its own MongoDB, because it needs a replica set to persist transformation rules. You run the FHIR proxy mediator — a separate Java service — because OpenHIM core cannot speak FHIR natively. And you write your own HL7v2 mediator, because that too is outside the core.
That is five running processes, two separate databases (MongoDB for the core and another MongoDB replica set for the mapping mediator), and a maintenance matrix that grows combinatorially. When Node.js releases a security advisory, you patch three separate Node.js services. When MongoDB has a vulnerability, you patch two separate MongoDB installations.
“The biggest hidden cost of OpenHIM is not the software itself — it is the operational overhead of running five loosely-coupled services where one JAR would do.”
And then there is the question nobody wants to answer directly: OpenHIM does not natively support JWT Bearer tokens or SMART on FHIR scopes. In 2026, when the entire digital health ecosystem is moving toward OAuth2 and SMART App Launch — when every major EHR vendor, every national framework, and every clinical app standard is built on JWT and SMART — OpenHIM’s authentication story is still essentially mTLS and Basic auth. JWT validation has to be bolted on externally, which means yet another service in your deployment.
What JamGateway Is — and What It Isn’t
JamGateway is not a fork of OpenHIM. It is a ground-up reimplementation of the OpenHIM feature set in the Java ecosystem, using the best tools available in 2025 for each concern:
- Routing → Apache Camel 4.8 (dynamic routing, fan-out, MLLP, retry, dead-letter)
- FHIR → HAPI FHIR 7.4 (typed R4 client, XML↔JSON native, PDQm/MHD/PIXm)
- Security → Spring Security OAuth2 Resource Server (JWT, SMART scopes, mTLS, Basic auth)
- Admin UI → Thymeleaf + HTMX (9 screens, no npm, no Angular build step)
- Audit → IHE ATNA RFC 5425 TLS syslog + IHE BALP FHIR R4 AuditEvent (owned code, no IPF dependency)
- Alerts → Spring Mail + Twilio SMS (email and SMS on configurable error rate thresholds)
What it isn’t: a general-purpose API gateway. JamGateway is specifically built for the health interoperability domain. It knows about FHIR resources, IHE transactions, HL7v2 messages, SMART on FHIR scopes, and OpenHIM mediators. It is not trying to compete with Kong or Tyk for generic HTTP routing at 10,000 RPS.
Why the Codebase Is 3× Smaller — Not Because We Cut Features
When I tell people JamGateway is 5,872 lines vs OpenHIM’s ~18,000, the first question is always: “What did you cut?” The honest answer is: nothing meaningful. The difference is entirely architectural — not features removed, but framework choice eliminating the need to write code at all.
Spring Boot gave us ~8,000 lines for free
OpenHIM was written at a time when Node.js did not have mature enterprise frameworks. So the core team built session management, scheduling, email, JWT token handling, certificate management, and caching themselves. That is roughly 8,000 lines of infrastructure code that predates modern frameworks. JamGateway gets all of it from Spring Boot via configuration:
<span class="code-comment"># Session management</span>
<span class="code-key">spring.session.store-type</span>=<span class="code-val">redis</span> <span class="code-comment"># or memory</span>
<span class="code-comment"># Email alerts</span>
<span class="code-key">spring.mail.host</span>=<span class="code-val">smtp.moh.gov</span>
<span class="code-key">hie.alerts.enabled</span>=<span class="code-val">true</span>
<span class="code-key">hie.alerts.recipients</span>=<span class="code-val">ops@moh.gov</span>
<span class="code-comment"># JWT validation — one line to point at your Keycloak</span>
<span class="code-key">spring.security.oauth2.resourceserver.jwt.issuer-uri</span>=<span class="code-val">https://auth.example.com/realms/health</span>
<span class="code-comment"># Rate limiting — in-memory or Redis</span>
<span class="code-key">jam.rate-limit.store</span>=<span class="code-val">redis</span>
That is configuration, not code. Spring Boot’s auto-configuration handles the rest.
Apache Camel gave us the HL7v2 mediator and the FHIR proxy for free
In OpenHIM, connecting an HL7v2-speaking hospital requires deploying the mapping mediator — a separate Node.js service with its own MongoDB. In JamGateway, it is a Camel route:
<span class="code-comment">// Hl7v2MllpBridgeRoute.java — the entire HL7v2 MLLP bridge</span>
<span class="code-key">from</span>(<span class="code-val">"mllp://0.0.0.0:{{jam.mllp.port}}"</span>)
.process(hl7v2Parser::parse) <span class="code-comment">// HAPI HL7v2 2.6</span>
.process(hl7v2ToFhirMapper::map) <span class="code-comment">// ADT A04/A08/A40, ORU R01 → FHIR R4</span>
.to(<span class="code-val">"{{jam.fhir.server.url}}/Patient"</span>)
.process(atnaService::auditAdtFeed); <span class="code-comment">// IHE ATNA ITI-8</span>
That route — 8 lines — replaces the mapping mediator, its MongoDB cluster, and the custom HL7v2 mediator that every OpenHIM deployment ends up writing. The FHIR proxy mediator is similarly unnecessary because HAPI FHIR’s client handles XML↔JSON conversion internally.
Owning the IHE code eliminated the IPF dependency
The standard Java approach to IHE ATNA is to pull in the IPF (Integration Profile Framework) library. IPF is a powerful but heavy Camel-based framework that can bring in 40+ transitive dependencies and constrains your Camel version. We extracted the IHE ATNA code directly from the IPF source and own it in JamGateway’s codebase — three classes, ~400 lines total. No IPF. No dependency risk. If IPF stops being maintained tomorrow, JamGateway is completely unaffected.
What JamGateway Does That OpenHIM Simply Cannot
Parity is one thing. But there are three areas where JamGateway is categorically ahead of OpenHIM — not because OpenHIM made bad choices, but because these capabilities either did not exist or were not priorities when OpenHIM was architected.
1. JWT Bearer tokens and SMART on FHIR — native, not bolted on
SMART App Launch is the standard for how clinical applications authenticate with healthcare APIs. It builds on OAuth2 and OpenID Connect, using JWT Bearer tokens with scopes like patient/Patient.read or system/*.write. Every major EHR — Epic, Cerner, MEDITECH — exposes SMART-compliant endpoints. Every national digital health framework that launched after 2018 mandates SMART compliance.
OpenHIM does not validate JWT tokens. It was built around mTLS and Basic auth, which are fine for hospital-to-HIE connections but completely unworkable for a third-party app ecosystem. Adding JWT validation to OpenHIM requires an external service that validates the token before the request reaches OpenHIM — yet another process to maintain.
In JamGateway, JWT validation is a Spring Security filter chain that runs before any routing happens:
<span class="code-comment">// Chain 2: /fhir/** — stateless, JWT required</span>
<span class="code-key">http</span>.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt.jwtAuthenticationConverter(smartJwtConverter())));
<span class="code-comment">// SmartScopeEnforcer.java — runs inside the Camel route</span>
<span class="code-comment">// Checks patient/Patient.read before the HAPI FHIR call</span>
<span class="code-key">if</span> (!auth.getAuthorities().contains(<span class="code-val">"SCOPE_patient/Patient.read"</span>)) {
<span class="code-key">throw new</span> FhirAuthorizationException(<span class="code-val">"Insufficient SMART scope"</span>);
}
The .well-known/smart-configuration discovery endpoint is served by SmartConfigController — a 40-line Spring MVC controller. Any SMART-capable app pointed at JamGateway will discover it correctly and initiate the OAuth2 flow.
2. IHE BALP — FHIR R4 AuditEvent, not just syslog
OpenHIM emits IHE ATNA audit events to a TLS syslog repository. That is the correct IHE behaviour for 2014. But the IHE BALP (Basic Audit Log Patterns) supplement — published in 2022 — defines how audit events should be represented as FHIR R4 AuditEvent resources and posted to a FHIR server. This is the direction the entire IHE community is moving: away from DICOM-encoded syslog and toward structured FHIR R4 resources that can be queried, analysed, and displayed in standard FHIR tooling.
JamGateway emits audit events to three destinations simultaneously:
- RFC 5425 TLS syslog — IHE ATNA compliance, for legacy audit repositories
- Local PostgreSQL table — browsable in the admin console’s ATNA viewer
- FHIR R4 AuditEvent POST — IHE BALP, for FHIR-native audit infrastructure
The BALP destination is enabled with one configuration flag and points at any FHIR R4 server. OpenHIM does not have this at all.
3. HL7v2 and IHE FHIR transactions — native, not via mediators
This is the one that surprises people most. OpenHIM is often described as supporting HL7v2 and IHE FHIR transactions — but in practice, it supports them via mediators. The core itself is agnostic; it routes HTTP requests. The HL7v2 parsing, the FHIR transaction handling, and the IHE profile compliance live in separate mediator services.
JamGateway handles all of this in the core, natively. ADT A04, A08, and A40 messages arrive over MLLP, get parsed by HAPI HL7v2, mapped to FHIR R4 Patient resources by Hl7v2ToFhirMapper, and posted to the FHIR server — all in one Camel route, in one JVM, with one audit trail. PDQm (ITI-78), MHD (ITI-65/67/68), and PIXm (ITI-83) are typed HAPI FHIR client calls, not HTTP string manipulation.
The Feature Matrix — Honest, Verified, Line by Line
I want to be careful here. There is a lot of marketing material online comparing healthcare middleware products where the comparison is based on documentation rather than code. The table below is based on the JamGateway source code — verified line by line, class by class, against the OpenHIM feature set as documented at openhim.org and as observed in the GitHub repositories.
| Feature | OpenHIM | JamGateway |
|---|---|---|
| Routing | ||
| Channel routing + hot-reload | ✓ | ✓ ChannelRegistry + @Cacheable |
| Fan-out secondary routes | ✓ | ✓ ChannelFanOutProcessor |
| Rate limiting — single node | ✓ | ✓ InMemoryRateLimitStore (token bucket) |
| Rate limiting — clustered | ✓ MongoDB | ✓ RedisRateLimitStore (INCR/EXPIRE) |
| Security | ||
| mTLS + Basic auth per channel | ✓ | ✓ ChannelAuthInterceptor + AliasKeyManager |
| Certificate management | ✓ | ✓ CertificateManagementService (JDK-only) |
| JWT Bearer token validation | ✗ not native | ★ Spring Security OAuth2 Resource Server |
| SMART on FHIR scopes + discovery | ✗ none | ★ SmartScopeEnforcer + .well-known endpoint |
| Transactions | ||
| Full tx log + body storage | ✓ | ✓ TransactionLogProcessor + 32KB truncation |
| Manual + bulk transaction rerun | ✓ | ✓ TransactionRerunService + originTxId chain |
| Audit | ||
| IHE ATNA RFC 5425 TLS syslog | ✓ | ✓ AtnaService (owned code, no IPF) |
| IHE BALP FHIR R4 AuditEvent | ✗ none | ★ BalpAuditEventMapper + BalpSender |
| Email + SMS alerts | ✓ | ✓ AlertService (Spring Mail + Twilio) |
| IHE / HL7v2 | ||
| HL7v2 ADT + ORU over MLLP | ~ via mediators | ★ native Camel MLLP + HAPI HL7v2 |
| PDQm / MHD / PIXm | ~ via mediators | ★ native HAPI FHIR R4 client |
| Mediators | ||
| OpenHIM mediator protocol | ✓ full | ✓ MediatorService — full spec compatible |
| Mediator marketplace page | ✓ openhim.org | ✗ not yet — sole remaining gap |
✓ Parity · ~ Partial / via mediators · ✗ Gap · ★ JamGateway ahead
The Inside of the JAR — Layer by Layer
Understanding what lives inside the JAR is important if you are evaluating this for production use. A fat Spring Boot JAR can be a black box if you do not understand its internal organisation. JamGateway’s 43 Java files are organised into four clearly separated layers:
The Security Layer runs first — every request passes through Spring Security before Camel sees it. JWT validation, SMART scope extraction, mTLS client certificate verification — all done at the filter chain level. Camel routes are never invoked for an unauthenticated request.
The Routing Layer is where Apache Camel lives. FhirGatewayRoute handles /fhir/** — it resolves the channel from ChannelRegistry, enforces per-channel rate limits, fires the primary HTTP call to the channel target, and optionally fans out to secondary routes asynchronously. Hl7v2MllpBridgeRoute listens on a separate MLLP port for HL7v2 messages.
The Audit Layer is invoked from within the routing layer. Every transaction writes to TransactionLogProcessor (Postgres), every IHE transaction writes to AtnaService (TLS syslog + Postgres + optionally BALP). AlertService runs on a @Scheduled every 60 seconds and checks per-channel error rates.
The Admin Layer is a separate Spring Security filter chain (@Order(1)) handling /admin/** — session-based, form login, CSRF enabled. The nine Thymeleaf + HTMX screens communicate with service classes that are shared with the routing layer.
Chain 1 (/admin/**): session-based, form login, CSRF enabled. Chain 2 (/fhir/**, /mediators/**): stateless, JWT Bearer, CSRF disabled. This separation is critical — the admin console uses traditional web session management while the FHIR API uses stateless token validation. They never interfere with each other.
Why This Matters for National-Scale Health Infrastructure
I want to talk about something that is not in most technical blog posts about health middleware: what this actually means when you are building infrastructure that a country’s healthcare system will depend on.
National health information exchanges are not like typical enterprise software. They are public infrastructure, like roads or the electrical grid. They run 24/7. They connect thousands of facilities. When they go down, clinicians cannot access patient records. When they emit wrong audit data, there are regulatory consequences. When a security vulnerability is not patched because the upgrade path is too painful, patient data is at risk.
The argument for simpler deployments is not primarily about developer convenience. It is about the long-term maintainability of critical public infrastructure by teams that will change over time, with budgets that are always constrained, in environments where the original engineers may not be available when something breaks three years from now.
A single JAR that starts with java -jar jam-gateway.jar, configured entirely via environment variables, backed by a single PostgreSQL instance — this is infrastructure that a competent junior engineer can understand, maintain, debug, and recover from a failure. The alternative — five services, two MongoDB clusters, a custom HL7v2 mediator, an nginx layer — is infrastructure that requires institutional knowledge to operate.
If you are evaluating the OpenHIE reference implementations as a stack — OpenHIM + OpenCR + OpenMRS — please check the commit history on OpenCR before building on it. OpenCR (the client registry) was developed with USAID MEASURE Evaluation funding. That funding is gone. The repository shows minimal activity. OpenHIM itself shows declining commit frequency in 2024-2025. Building national infrastructure on software with uncertain maintenance futures is a strategic risk that deserves honest evaluation.
The Roadmap: What Comes After the Gateway
The gateway is the entry point to a health information exchange — the routing and security layer. But it is one of six components that a complete national HIE needs. Here is the honest picture of where this sits in the larger architecture, and what needs to be built or connected next.
The facility registry is already done (for most countries)
Many national health programmes have already deployed some form of facility registry — a master list of healthcare facilities with their codes, locations, and capabilities. The IHE mCSD (Mobile Care Services Discovery) profile defines how to expose this as a FHIR R4 Location/Organization resource endpoint. Connecting JamGateway to an existing facility registry is a channel configuration task — no new software.
The shared health record is the FHIR server you already have
The NeHR — National Electronic Health Record — is the longitudinal patient record across all facilities. In FHIR terms, this is a FHIR R4 JPA server. HAPI FHIR is already in the JamGateway stack as the FHIR client. Deploying HAPI FHIR JPA as the backend FHIR server gives you the NeHR. The gateway routes to it, the audit trail tracks every access, the SMART scopes control who can see what. The software is already there — the work is governance, data quality, and connectivity, not software development.
The client registry — this is the next real engineering problem
This is the piece that most genuinely blocks everything else. Without a Master Patient Index, the gateway can route requests correctly, but the receiving systems cannot confirm that the patient at Facility A and the patient at Facility B with slightly different name spellings are the same person. Duplicate records accumulate. Clinical decisions get made on incomplete histories.
The right answer here is not to deploy OpenCR (Node.js, questionable maintenance), not to deploy SantéMPI (capable but heavyweight). The right answer is to enable HAPI FHIR’s MDM (Master Data Management) module on the same FHIR server already in the stack. HAPI MDM adds golden record management, probabilistic patient matching with configurable JSON rules, Patient/$match operation, and the :mdm search parameter expansion. IHE PMIR (ITI-93 Mobile Patient Identity Feed and ITI-94 Subscribe to Patient Updates) gives you push-based identity federation — when a patient record changes at any facility, all subscribed systems receive the update. This is the future direction of the IHE standards, and it is implementable on infrastructure you already own. A blog post on this is coming.
Getting Started — Honest Quick Start
Here is what an actual deployment looks like. Not the aspirational docker run from the marketing page — the real thing.
<span class="code-comment"># docker-compose.yml</span>
<span class="code-key">services</span>:
<span class="code-key">jamgateway</span>:
<span class="code-key">image</span>: <span class="code-val">ghcr.io/your-org/jam-gateway:1.0</span>
<span class="code-key">environment</span>:
<span class="code-key">JAM_FHIR_SERVER_URL</span>: <span class="code-val">http://hapi-fhir:8080/fhir</span>
<span class="code-key">SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI</span>: <span class="code-val">https://auth.example.com/realms/health</span>
<span class="code-key">JAM_ADMIN_PASSWORD</span>: <span class="code-val">change-me-in-production</span>
<span class="code-key">JAM_ATNA_REPOSITORY_HOST</span>: <span class="code-val">audit.example.com</span>
<span class="code-key">JAM_ALERTS_ENABLED</span>: <span class="code-val">true</span>
<span class="code-key">JAM_ALERTS_RECIPIENTS</span>: <span class="code-val">ops@example.com</span>
<span class="code-key">ports</span>: [<span class="code-val">"8080:8080"</span>, <span class="code-val">"2575:2575"</span>] <span class="code-comment"># HTTP + MLLP</span>
<span class="code-key">depends_on</span>: [<span class="code-val">postgres</span>]
<span class="code-key">postgres</span>:
<span class="code-key">image</span>: <span class="code-val">postgres:16</span>
<span class="code-key">environment</span>:
<span class="code-key">POSTGRES_DB</span>: <span class="code-val">jamgateway</span>
<span class="code-key">POSTGRES_PASSWORD</span>: <span class="code-val">change-me</span>
<span class="code-comment"># Two services. That's it. Admin console at http://localhost:8080/admin</span>
Admin console is at http://localhost:8080/admin. SMART discovery endpoint is at http://localhost:8080/.well-known/smart-configuration. FHIR gateway is at http://localhost:8080/fhir/ — JWT Bearer token required. MLLP port 2575 for HL7v2.
An OpenHIM mediator connects identically — it posts its registration JSON to POST http://localhost:8080/mediators using openhim-mediator-utils. The endpoint is protocol-compatible.
Should You Switch From OpenHIM to JamGateway?
If you are running a stable OpenHIM deployment with a team that knows it well and no immediate requirement for JWT/SMART or FHIR-native audit — probably not immediately. Running production software is about stability, and “it works” is a legitimate reason to keep something running.
If you are evaluating OpenHIM for a new deployment, or facing a major OpenHIM upgrade, or building a new national HIE component from scratch — yes, evaluate JamGateway seriously. The operational simplicity case is strong. The SMART on FHIR case is even stronger if your roadmap includes third-party clinical app access. The BALP case matters if your country has IHE compliance requirements for FHIR-era audit infrastructure.
The honest summary: JamGateway is not more capable because it is cleverer. It is simpler because it was built ten years later, using frameworks that absorbed a decade of hard-won lessons about enterprise Java, health interoperability, and the operational realities of running critical infrastructure with small teams. OpenHIM built those lessons itself. JamGateway just started with them already in the box.
The code is at GitHub, Apache-licensed, 43 Java files, 5,872 lines. The mediator protocol is fully compatible — if you run OpenHIM today, your mediators will register with JamGateway without a code change. The upgrade path is a configuration change, not a rewrite.
And the client registry is coming next. But that is a separate blog post.
Leave a Reply