When does a HAPI FHIR plain server beat the JPA server?
If the existing clinical system or file store must remain the source of record, the plain server is the model to evaluate first. Under that model you write one resource provider class per resource type, and each class reads from and writes to the system you already own. If HAPI should own storage and the schema, the JPA server is the better starting point, because it ships a database-backed RESTful FHIR server with the persistence layer already built. Either can be the correct answer. The deciding question is who owns the data and the schema, not which product is more capable.
The plain server fits when your team controls the legacy data model, when write-back to that system must follow its own business rules, or when the data lives in files or a system that should not be copied into a second store. You take on more code: mapping between your internal model and FHIR resources, search parameter handling, paging, and error behavior for every resource you expose. Ask your developers to list the search parameters clinical consumers need and confirm each one can be served efficiently from the legacy store before committing. Also confirm the FHIR versions your consumers require, since the plain server is not tied to the JPA server's version set.
The JPA server fits when HAPI can hold the data, when you want terminology services, validation, MDM or partitioning without writing them yourself, or when no authoritative clinical system needs to stay the system of record. Its starter defaults to an embedded H2 database, so a production deployment needs an explicit choice of PostgreSQL or Microsoft SQL Server, since MySQL is not supported. For a FHIR facade over a legacy system, a common middle path is to keep the legacy system authoritative and reserve the JPA server for a read-optimized copy, but that copy then needs its own synchronization and reconciliation plan. Confirm with your compliance and data governance leads which copy counts as authoritative before the architecture is fixed.
What does the HAPI FHIR JPA server handle that a plain server leaves to you?
This table compares what your team writes, runs and maintains under HAPI FHIR's plain server model against the database-backed JPA server model. Read it as a responsibility split: the rows show where the JPA server takes work off your team and where your team still carries the load. The main takeaway is that the JPA server saves storage and service-layer work, but it also brings a database, deployment and partitioning choices you must make and operate.
| Factor | Plain server (you write the resource providers) | JPA server (HAPI supplies the storage layer) |
|---|---|---|
| What you write | One resource provider class per resource type, mapping FHIR operations to your own data source | Mostly configuration and any custom interceptors, providers or operations you need beyond the built-in resources |
| Where resources are stored | In whatever store you connect, such as your own database, an existing clinical system or files | In a relational database accessed through the JPA API |
| Supported databases | Determined by your own data access code, so there is no HAPI database list to rely on | Embedded H2 by default; PostgreSQL and Microsoft SQL Server for production; MySQL is not supported |
| Deployment options | Any Java servlet setup you build and run around your code | Docker image, Docker Compose, Spring Boot, WAR on Tomcat, Jetty or Wildfly, or Kubernetes with a Helm chart |
| Partitioning | Not provided; you design any tenant or region separation in your own store | Resources placed in partitions for multitenancy, separating data sources or grouping by region |
| Terminology services | Not provided by default; you must implement any code system lookups you need | Included as part of the JPA server's documented feature set |
| Validation | Not provided by default; you decide how and whether to validate payloads | Included as part of the JPA server's documented feature set |
| MDM | Not provided; you would build any patient matching or merge logic | Included as part of the JPA server's documented feature set |
| FHIR version coverage | Confirm in HAPI's plain server documentation which FHIR versions your chosen approach supports | DSTU2, DSTU3, R4, R4B and R5 per HAPI's JPA server documentation |
What you write in a plain server and what you inherit in the JPA server
The plain server and the JPA server split the same FHIR work in different places. Each card below names one component, who builds it in each model, and what that ownership costs the team to keep running over time.
Resource provider classes
In the plain server you write one resource provider class per resource type, with the read, search and create methods that map HTTP requests onto your code. In the JPA server, the provider layer for supported resource types ships with HAPI, so your team mainly writes providers for custom operations or resource types HAPI does not handle. Maintenance in the plain model grows with every resource type you expose, so check how many types your consumers actually need before committing.
Search and paging logic
In the plain server, your team implements search parameters, paging and sorting against your own store, so every parameter a client may send is code you test and keep working. The JPA server supplies search parameter handling, indexing and paging across the FHIR data model, and your team tunes it rather than rebuilds it. Confirm which search parameters your consumers depend on and test them against realistic query volumes.
Mapping between FHIR and your schema
In the plain model, your team translates between FHIR resources and the fields, codes and relationships in your legacy schema, and decides how to represent gaps such as missing data or unmapped codes. The JPA server stores FHIR resources in its own database schema, so mapping work moves to the edges, where data is imported or exported. Ask who owns terminology mappings and who approves changes to them.
Data access layer
In the plain model, you own the data access layer end to end: connections, transactions, migrations, indexing and the failure behavior of the source system. The JPA server runs its own persistence layer on a supported database, and your team owns its operation: database sizing, backups, upgrades and schema migrations between HAPI releases. Check which database your operations team already runs, since PostgreSQL and Microsoft SQL Server are the supported production choices for the JPA starter.
Server configuration and deployment
The plain server is configured as your own application, so your team chooses the container, the servlet setup and the release process. The JPA server ships configuration for several deployment routes, including Docker, Spring Boot, WAR on Tomcat, Jetty or Wildfly, and Kubernetes with a Helm chart, but partitioning, validation and terminology settings still need deliberate choices. Confirm the JDK version each HAPI release requires before you set your build image.
Security and audit controls
HAPI does not provide one complete security layer. In either model your team wires authentication, the AuthorizationInterceptor, consent and search narrowing, CORS and BALP audit logging, so the plain server gives you full control of where these sit while the JPA server requires you to attach them to the existing pipeline. Map each access rule to a test before go-live, because a gap here exposes data regardless of which model you choose.
How a FHIR facade over a legacy system is built with the plain server
The plain server is the right starting point when the data you must expose already lives somewhere else. With HAPI FHIR's plain server, you create a FHIR endpoint over an arbitrary source, such as a database of your own design, an existing clinical system or a set of files, by writing one resource provider class per resource type. The first and largest piece of work is the mapping: for each FHIR resource you publish, you decide which legacy tables, fields, codes and reference keys feed it, and how each value is transformed. A legacy patient identifier may need to become an Identifier with a system URI, a local status code may need a terminology lookup into a standard code system, and a free-text allergy field may need to be split or held back. Write the mapping down as a specification before writing code, and validate it against a sample of real records from your own data, not only against well-formed examples. The example below is illustrative only: a legacy PAT_MASTER row with a MRN column might map to Patient.identifier with a value set from MRN, but your system's keys, codes and edge cases will differ.
Next, decide which searches the legacy system can answer efficiently. Each resource provider exposes search parameters, and every one you support has to be implemented against your data store. In a legacy system that was not designed for FHIR queries, a search such as finding observations by patient and code can turn into a full table scan if no index exists for that path. Pull the list of search parameters your consuming applications actually use, test each one against production-sized data in a non-production copy, and reject or narrow the ones that cannot be answered within your performance budget. This is also where you find out how much of the FHIR search specification you will not implement at all, and you should confirm the exact search behavior your clients rely on with the HAPI FHIR documentation and your own tests. Finally, keep the legacy system authoritative. A facade that caches or copies records creates a second source of truth, and that is the failure most likely to hurt clinical data quality. Decide, per resource, whether writes pass through to the legacy system, whether the facade is read-only, and how updates made directly in the legacy system become visible to FHIR clients. Security also belongs in this design, because HAPI FHIR states that it does not provide a single one-size-fits-all security layer. Plan your own authorization and audit controls, using the building blocks it offers such as the AuthorizationInterceptor and BALP audit logging, and have your security team verify them.
How to deploy the HAPI FHIR JPA server
The hapiproject/hapi image is the fastest way to run the starter as a container, and it starts with the embedded H2 database, which suits evaluation and test environments. For production, you supply a PostgreSQL or Microsoft SQL Server connection through configuration, because the starter does not support MySQL.
- Configuration: database URL, credentials and server settings are passed as environment variables or mounted configuration, so the image stays the same across environments.
- Image provenance: your team owns pulling, scanning and pinning the image tag, and it must confirm which HAPI FHIR version that tag contains.
- Persistence: unless you mount storage or point the server at an external database, data is tied to the container lifecycle.
- Patching: moving to a new image or HAPI version is a rollout your team runs, including regression testing of your resource providers, search behavior and interceptors.
Before go-live, confirm the image tag, the JDK it bundles and the database driver version against the release notes for your chosen HAPI FHIR version.
Docker Compose defines the HAPI server, a database container and their networking in one file. It is the usual step up from a single container for a team that wants a repeatable stack on one host. The operations team still owns that host, the volumes and the secrets.
- Multi-service definition: the FHIR server and PostgreSQL run as linked services, with the connection settings in the file or an environment file.
- Volumes: database data must sit on a named or bind-mounted volume with backup routines, or it can be lost when the stack is recreated.
- Secrets: database passwords and TLS material belong in a secrets mechanism your team controls, and they should stay out of version control.
- Single-host limits: Compose runs on one host, so availability depends on that host, and scaling beyond it calls for a different platform.
Before relying on the stack, confirm that the PostgreSQL major version and the HAPI FHIR release you pin are both on your support list, and test a database restore.
The starter is itself a Spring Boot application, so running it under Spring Boot gives your team standard JVM operations: an executable artifact, externalized configuration and familiar logging and monitoring hooks. This model suits teams that already run Spring services and want to add their own interceptors, authentication filters or custom providers in the same codebase.
- Build ownership: your team builds and versions the application, which means it owns dependency updates and the JDK on the runtime.
- Configuration: datasource and server settings come from externalized properties, so credentials can come from your secrets store.
- Extension code: custom interceptors, such as wiring for the AuthorizationInterceptor, live in your repository, so they fall under your change control and testing.
- Runtime tuning: the JVM needs memory and garbage collection settings, and someone must monitor them over time.
Confirm the JDK requirement for your exact HAPI FHIR version first, since HAPI FHIR versions documents JDK 17 for 8.2.0, and pin the Spring Boot version your build uses.
Packaging the server as a WAR for Tomcat, Jetty or Wildfly suits organizations that already standardize on an application server and have operators who manage it. The servlet container, its TLS termination, its clustering and its patch cycle become your team's responsibility alongside HAPI.
- Container compatibility: confirm which servlet container version your HAPI release supports, since each application server has its own version requirements.
- Deployment process: WAR deployments follow your existing application server change process, including hot deployment rules and rollback steps.
- Configuration location: settings may live in the container's configuration directory, server properties or environment variables, so document where each one sits.
- Operations skills: the team needs people who understand the chosen container, which can be a different skill set from the one that runs your FHIR data.
Before choosing this path, confirm in the HAPI FHIR documentation that your container and HAPI version are supported together, then test a full deployment in staging.
The starter includes a Helm chart for Kubernetes, which fits teams that already run clusters and want the FHIR server managed alongside other services. Kubernetes adds replicas, rolling updates and health checks, and it also adds cluster operations that your team must own.
- Chart values: image tag, database connection, ingress and resource limits are set through chart values, so review them in version control before each release.
- Database placement: confirm how the chart provides the database and whether it runs inside or outside the cluster, because database high availability is a separate design decision.
- Cluster controls: network policy, secrets management, pod security and TLS at ingress are yours to configure and to evidence.
- Upgrades: a Helm upgrade changes the running server, so pin the chart and image versions and rehearse rollbacks.
Before adopting the chart, confirm the chart version, the HAPI FHIR image tag and its JDK, using the starter repository, and check the result against your cluster policies.
Who runs upgrades, backups, monitoring and database work in each model
This table shows which party carries day-to-day operations in the plain server and the JPA server models, from upgrades and backups to database administration. Read each row as a question to put to HAPI, your database team and your legacy system owners before you choose a model. The main takeaway is that the plain model shifts availability and data store ownership onto your team, while the JPA model shifts schema and migration work onto you.
| Operational area | Plain server (your own data store) | JPA server (HAPI-managed database layer) |
|---|---|---|
| Upgrades to HAPI FHIR | Your team upgrades the library and re-tests every resource provider against the new API, since your code is the server logic. | Your team upgrades the starter or WAR and checks the database schema changes that come with each release. Confirm the upgrade notes for your target version with HAPI. |
| Backups and restore | Your team owns backup and restore of the data store and must confirm that restored data still matches what the providers return. | Your database team backs up the JPA database. Confirm with HAPI and your DBA which tables must be restored together, including search indexes. |
| Database schema and migrations | Schema is whatever your legacy system or design defines. Your team manages any changes and their effect on existing clients. | HAPI creates and changes the JPA schema. Your team must plan migrations, test them on a copy of production data and confirm the supported database versions. |
| Database administration | Your team handles tuning, indexing, capacity and access to the underlying store, which may be a system you do not control today. | Your DBA manages PostgreSQL or Microsoft SQL Server, since H2 is only the embedded default. Confirm the database skills and sizing your environment needs. |
| Monitoring and alerting | Your team monitors the endpoint, provider logic and the legacy system together, so an outage in either one shows up as a FHIR failure. | Your team monitors the Java application and the database. Confirm which HAPI metrics and audit outputs you can use, such as BALP audit logging, with your monitoring stack. |
| Availability of the legacy system | Your team owns the availability of the legacy system behind the facade, and the FHIR endpoint cannot be more available than that system. | The JPA store is a separate copy, so legacy availability matters mainly during data synchronization. Confirm how your sync process behaves when the legacy system is down. |
| Skills the team needs | Java and FHIR modeling, plus integration skills for the legacy system and its data formats. | Java, Spring or servlet container operations, database administration and FHIR modeling. Confirm the mix with your platform and database teams. |
How hard is it to move from a plain facade to the JPA server later?
The sequence below shows how a team moves FHIR traffic from a custom resource provider layer to the HAPI JPA server without a hard cutover on day one, and what evidence each stage must produce before the next begins.
Confirm the resource coverage the facade must serve
Before any migration work, the integration team lists every FHIR resource type and search parameter that clients actually call, using gateway logs or client contracts rather than the original design document. Architects and the client owners sign off on that list. The step is done when each resource type is marked as in scope or out of scope for the JPA server, and each search parameter has a named owner who confirmed whether the JPA server supports it as used. Anything that does not map cleanly goes into an open issues log so it is not discovered during cutover.
Decide which system holds the source of record
Decide, resource by resource, whether the legacy system or the JPA server is the authoritative store during the transition. This decision drives write paths, conflict handling and who is paid to build what. If the legacy system stays authoritative, the JPA server becomes a read-optimized projection that must be kept in sync. If the JPA server becomes authoritative, the facade's write providers must be retired or redirected. The step is complete when a written ownership table exists, is approved by the clinical and security owners, and names the sync direction for each resource type.
Plan data migration and identifier mapping
Build a mapping table between legacy record identifiers and the logical and server-assigned identifiers the JPA server issues, and decide how references between resources will be rewritten. Clients that stored legacy identifiers need either a translation layer or a planned update, so this table is the most sensitive artifact in the project. Volume matters here: the number of records, attachments and historical versions determines how long bulk loads take and how much reconciliation work follows. Confirm the data volume with the legacy owner early, because it drives the migration tooling and the parallel run window. The step closes when a dry-run load produces a reconciliation report that the data owner accepts.
Map custom search behavior to the JPA server
Custom search logic written into the facade, such as bespoke parameters, joins across legacy tables or non-standard result ordering, is the most common source of switching cost. Compare each custom behavior against the JPA server's supported search features and record the gap. Some gaps can be closed with custom search parameters or interceptors, and others need a client change. Estimate the rework by counting the gaps, not by assuming a fixed effort per gap, and confirm with the HAPI FHIR documentation and your own test cases which behaviors the JPA server reproduces. The step is done when every custom search has a documented equivalent, an accepted workaround, or a client change request.
Run both systems in parallel for validation
Route a copy of read and, where applicable, write traffic to the JPA server while the facade continues to serve production clients. Compare responses for the same requests and log every difference in payload, status code, paging behavior or search results. Clinical and integration owners review the differences by category, and the team fixes mapping and configuration issues before the next pass. This stage also tests the operating model: backups, database upgrades and monitoring for the JPA server must already work on the target database, which HAPI documents as PostgreSQL or Microsoft SQL Server for production. The step is complete when a defined run of comparisons produces no unexplained differences in the in-scope resource types.
Migrate client dependence on legacy identifiers
Work with each consuming application to move from legacy identifiers to the identifiers the JPA server issues, or to confirm that a translation layer will remain in place for a defined period. Track each client as a separate line item with its owner, its release schedule and its rollback plan. Clients that are not under your control need a formal notice and a compatibility window agreed in writing. The step is done when every client has a tested configuration against the JPA server and the translation layer, if any, is documented with an owner and a retirement condition.
Cut over and retire the facade
Switch production traffic to the JPA server during a change window agreed with the clinical and operations owners, and keep the facade ready for rollback until the agreed validation criteria hold in production. Confirm that audit logging, authorization interceptors and consent controls behave the same way on the new server, since HAPI provides building blocks rather than a single security layer. Once the rollback window closes, decommission the facade's providers, document the final ownership table and archive the reconciliation reports for auditors. The step is complete when the facade no longer receives production traffic, the rollback path has been formally closed, and the evidence pack is filed.
Where JPA partitioning helps and where security stays your job in both models
Partitioning is a JPA server feature, not something you build in a plain server. HAPI FHIR JPA partitioning places each resource in a partition. Teams use partitions for multitenancy, for separating data sources, or for grouping data by region. If you need one FHIR endpoint that keeps tenants or regional datasets apart inside a single database-backed deployment, that capability belongs to the JPA model. In a plain server, you would have to design an equivalent partitioning scheme in your own data store and enforce it in every resource provider. Before you commit, confirm with HAPI's partitioning documentation how partitions interact with the database schema you will run on PostgreSQL or Microsoft SQL Server, and test how a search that crosses partitions behaves in your environment.
Neither model gives you a complete security layer. HAPI FHIR says it does not provide one-size-fits-all security. It offers building blocks you assemble yourself: authentication interceptors, the AuthorizationInterceptor, consent and search narrowing interceptors, CORS support, and BALP audit logging. Those building blocks work the same way whether you run plain or JPA, so the choice of model does not transfer access control to the framework. Your team owns the mapping from identities to permitted resources and compartments, the handling of patient consent, and the audit trail that regulators will ask for. Write those rules down, then test them with negative cases such as a user requesting another tenant's patient, a search that omits a restricting parameter, and an unauthenticated read. Use HAPI's security introduction as the starting reference, and have your security team review the configuration before go-live.
What do buyers ask before choosing a HAPI FHIR model?
What drives the effort and cost of a plain HAPI FHIR server versus the JPA server?
The plain server cost is mostly the code you write: one resource provider class per resource type, plus the mapping from FHIR to your data store, search parameters, paging and error handling. The JPA server moves that work into a database-backed implementation that already covers the RESTful API, but you still pay for database operations, the Java runtime, deployment and the configuration of security and partitioning. Both carry recurring costs for upgrades and testing against new FHIR versions. Ask any quote to separate build effort, hosting, and ongoing maintenance, and to name which resource types and search parameters are in scope.
How long does a FHIR facade over a legacy system take to build?
No supplied figure covers this, so treat any duration as unknown until a written scope exists. Duration is set by the number of resource types, the complexity of the legacy data mapping, the search behavior your consumers need, the security model, and how many FHIR versions you must support. A plain server adds per-resource coding time, while the JPA server shifts time toward configuration, database sizing and integration testing. Ask the implementation partner for a phased plan with milestones and the assumptions behind each phase.
What does the work need from our own engineering staff?
Plan for Java development skills for either model, plus knowledge of your legacy system's data and access rules. The plain server asks your team to understand FHIR resource semantics closely, because they will write the mapping logic themselves. The JPA server asks for operational skills: running a Java service, managing a PostgreSQL or Microsoft SQL Server database, handling upgrades, and reviewing the HAPI FHIR security building blocks. HAPI FHIR states it does not provide a single one-size-fits-all security layer, so someone on your team must own authorization design. Request a written list of skills, named owners and handover documentation in any scope.
Can our team build and run this without outside help?
A team with Java, FHIR and database experience can build either model in-house, and the open-source project is free to use under the Apache Software License 2.0. The harder questions are whether you can sustain the work: keeping pace with HAPI FHIR releases, which required JDK 17 for version 8.2.0, testing security controls, and maintaining audit logging and backups. Commercial support for HAPI FHIR is available from Smile CDR, as the project README points to it. Decide early whether you want an outside partner for the initial build, for ongoing support, or for both, and write down which.
Which databases does the HAPI FHIR JPA server support for production?
The JPA server starter defaults to an embedded H2 database, which is suited to evaluation rather than production. For production, the starter supports PostgreSQL and Microsoft SQL Server, and it does not support MySQL. Confirm the exact database versions and driver support against the current HAPI FHIR documentation before you commit, since database support is a configuration and testing decision you own. Ask any implementation quote to state which database engine, version, backup approach and high availability setup are included.
Can a plain FHIR facade later move to the JPA server?
Moving is possible in principle, but it is a migration project, not a configuration change. A plain facade delivers FHIR endpoints over your own store, so the JPA server would need its own persistence, and you would have to migrate data, reconcile identifiers and re-test search behavior and access rules. Designing resource mapping and identifiers with that future in mind reduces the later cost. Ask the partner to document the data model, identifier strategy and test cases so a future move can be estimated from written evidence.
What should we request in a written scope before choosing a model?
Ask for a scope that lists the FHIR versions, resource types, search parameters, the legacy data sources, the target database, the deployment method, the security and audit requirements, and the test plan. Add the handover items, the upgrade responsibilities and the support terms. Because DeployOpen does not publish a price list, the quote follows from that written scope, and most engagements are fixed-scope with deliverables and timelines agreed before work starts. Compare two quotes on the same scope, not on headline effort.
How DeployOpen scopes, delivers and supports a HAPI FHIR deployment
DeployOpen plans, implements, integrates, hardens, documents and supports self-hosted open-source platforms inside the client's own infrastructure, so the choice between a plain server and the JPA server is settled against your data, network and compliance evidence before any build starts. Engagements are fixed-scope, with scope and deliverables agreed in writing first.
How we scope
- Inventory the source systems, data volumes and FHIR resource types the facade must expose.
- Record which FHIR versions, search parameters and write operations the written scope covers.
- Confirm the target database, hosting platform and partitioning needs before choosing plain or JPA.
How we build
- Implement resource providers over your data store, or configure the JPA server on a supported database.
- Add authentication, the AuthorizationInterceptor, consent and audit logging using HAPI's building blocks.
- Test conformance, security controls and failure behavior against the agreed scope before handover.
How we hand over
- Deliver architecture notes, configuration, deployment artifacts and runbooks for your own operators.
- Document the custom code, its ownership and the upgrade path for each HAPI FHIR release you adopt.
- Prepare control evidence and records that your independent auditors or assessors can review.
How we support
- Offer ongoing support, upgrades and continuous testing as a retainer after handover.
- Plan HAPI FHIR version upgrades, including JDK requirements, as a scoped and tested activity.
- Ask any provider, including DeployOpen, to confirm support windows and responsibilities in writing.

