Developer ← Insights

Building a FHIR Server with HAPI: From docker compose up to Production

HAPI FHIR is, in our experience, the de facto open-source FHIR server library of the Java ecosystem — an open-source project supported by Smile Digital Health that underpins countless custom implementations and commercial products [5]. It provides a complete FHIR server framework on Spring Boot with a JPA persistence layer on a relational database. One correction to a common assumption: the JPA server does not run on just any RDBMS — PostgreSQL is the usual production choice (Microsoft SQL Server and Oracle are also options, H2 is the out-of-the-box development default), and MySQL is explicitly unsupported as a deprecated option. If your organization standardizes on MySQL, that constraint belongs in your platform decision, not in your go-live surprise list.

Getting a server running locally is genuinely a one-liner. The hapi-fhir-jpaserver-starter project ships a Docker Compose setup pairing the server with PostgreSQL [1]:

git clone https://github.com/hapifhir/hapi-fhir-jpaserver-starter.git
cd hapi-fhir-jpaserver-starter
docker compose up -d

# FHIR endpoint: http://localhost:8080/fhir
curl http://localhost:8080/fhir/metadata

That gives you full CRUD, search, history, and a web test UI out of the box. The distance between that moment and a production deployment is the subject of the rest of this article.

Configuration That Actually Matters in Production

Most of the starter's behavior is driven by application.yaml, and four areas deserve architect-level attention before real data arrives:

  • Database and search parameter tuning: HAPI's JPA schema indexes search parameters into dedicated token/string/date/quantity tables, and every active search parameter adds write-time indexing cost. In our experience, disabling the built-in search parameters you will never query — and being deliberate about custom ones — measurably improves both ingestion throughput and table growth. Pair this with proper connection pool sizing and PostgreSQL tuning as you would any write-heavy JPA application.
  • Profile and IG loading: the starter can download and install implementation guide packages at startup directly from configuration, so your server validates and indexes against US Core, IPS, or your own published IG without custom code [1]:
    hapi:
      fhir:
        implementationguides:
          uscore:
            packageUrl: https://hl7.org/fhir/us/core/package.tgz
            name: hl7.fhir.us.core
            version: 6.1.0
            installMode: STORE_AND_INSTALL
  • Validation at write time: wire the RequestValidatingInterceptor (reject non-conformant writes with HTTP 422) or the RepositoryValidatingInterceptor (enforce profile declarations and validation at the storage layer) as covered in our validation article — the starter exposes both through configuration [4].
  • Expensive operations need guardrails: operations like Patient/$everything and MDM's $match can traverse large compartments; enable them deliberately, with timeouts and result limits appropriate to your data volumes, rather than accepting whatever a default exposes.

Security: Bring Your Own Authorization Server

HAPI deliberately does not bundle authentication. The intended architecture is an external OAuth 2.0 / SMART on FHIR authorization server — Keycloak is a common pairing in our experience — with HAPI enforcing authorization decisions through its interceptor chain. The centerpiece is the AuthorizationInterceptor: you subclass it, inspect the incoming request (typically the validated bearer token's claims and SMART scopes), and declare allow/deny rules, including compartment-scoped rules that confine a patient-level token to its own record [2]:

public class SmartScopeAuthorizationInterceptor extends AuthorizationInterceptor {
  @Override
  public List<IAuthRule> buildRuleList(RequestDetails theRequest) {
    // ... validate JWT, extract patient context and scopes ...
    return new RuleBuilder()
        .allow().read().resourcesOfType(Observation.class)
        .inCompartment("Patient", new IdType("Patient/" + patientId))
        .andThen()
        .denyAll()
        .build();
  }
}

One behavior worth knowing before load testing: for reads, the interceptor lets the query execute and then filters the response against the rules — a deliberately comprehensive design that also blocks _include/_revinclude tricks, but one with performance implications, since an unauthorized request can still make the server do the fetch work.

Multi-Tenancy, Scaling, and Subscriptions

Partitioning. If one server hosts data for multiple organizations, HAPI's partitioning feature stores a partition ID with every resource and gives you interceptor pointcuts to route each request: identify the partition on create, and on read — where the partition can be derived from the request tenant ID in the URL, request headers, or the authorized session context. AuthorizationInterceptor rules can then be scoped per tenant, closing the loop between identity and data isolation [3].

Scaling. The Spring Boot tier is stateless enough to scale horizontally behind a load balancer, which shifts the real scaling question to the database. For search-heavy workloads, in our experience the effective pattern is vertical headroom plus read replicas on PostgreSQL, aggressive search parameter hygiene (see above), and honest load testing: script realistic FHIR interactions — searches with _include, transaction bundles, history reads — in a tool like Apache JMeter or Gatling against production-sized data, because an empty database will happily lie to you about your indexing strategy.

Subscriptions. HAPI's JPA server supports FHIR Subscriptions with channels such as REST hook and WebSocket, pushing notifications when resources matching subscription criteria are created or updated — the building block for event-driven integration patterns. Note the standards evolution here: R4 subscriptions are criteria-string based, while the R5 model is topic-based (SubscriptionTopic); if push-based integration is central to your architecture, design your consumers against the topic-based direction of travel.

Starter Defaults vs. Production Posture at a Glance

Concern Starter Default Production Posture
Database H2 in-memory PostgreSQL with tuned pool, indexes reviewed, read replicas for search-heavy loads
Profiles / IGs Base FHIR only IG packages installed at startup; validation interceptors enforcing them on writes
Security Open endpoint External OAuth 2.0 / SMART server + AuthorizationInterceptor scope and compartment rules
Tenancy Single partition Partitioning enabled with tenant-aware interceptors and per-tenant authorization rules
Operations Broad defaults $everything/$match enabled deliberately with timeouts and limits; load tested with realistic data

Where CaboLabs Fits

The gap between docker compose up and a hardened clinical data platform is where projects burn their schedules — search parameter tuning, authorization design, partition strategy, and validation pipelines are each deceptively deep. CaboLabs builds and operates production FHIR infrastructure: HAPI FHIR server deployments and customization, SMART on FHIR authorization integration, IG and profile enforcement, and load testing against realistic clinical workloads — grounded in 20+ years of health information standards engineering across FHIR, HL7 v2, and openEHR. And when your architecture calls for a queryable, standards-based clinical repository alongside or beneath the FHIR API layer, our openEHR-native CDR Atomik provides that persistence foundation.

If you're standing up your first FHIR server, hardening one for production, or deciding how a FHIR API should relate to your clinical data repository, talk to us at cabolabs.com — we've already made the mistakes you're about to schedule.

References & Verifiable Sources

  1. HAPI FHIR Project: hapi-fhir-jpaserver-starter (GitHub) (Official starter project including the Docker Compose setup with PostgreSQL, the H2 development default, the note that MySQL is not supported as deprecated, and startup-time implementation guide installation via configuration; supports the setup and configuration sections).
  2. HAPI FHIR: Authorization Interceptor (Official documentation of rule-based authorization with RuleBuilder, compartment-scoped and tenant-scoped rules, and the response-filtering read authorization model with its performance implications; supports the security section).
  3. HAPI FHIR: Partitioning and Multitenancy (Official documentation of partition-based multi-tenancy, including partition identification via request tenant ID, headers, or authorized session context through interceptor pointcuts; supports the multi-tenancy section).
  4. HAPI FHIR: Built-In Server Interceptors (Official documentation of the request/response validating interceptors used to enforce profiles at write time, including HTTP 422 rejection; supports the validation configuration point).
  5. HAPI FHIR: HAPI FHIR Project Site (Official project homepage describing HAPI FHIR as a complete open-source implementation of the HL7 FHIR standard for Java, supported by Smile Digital Health; supports the project overview claims).

Do you have any questions?

Let us know how we can help you.

Company CaboLabs Health Informatics
Address Juan Paullier 995, Montevideo, Uruguay
Phone +598 99 043 145