HAPI FHIR plain server vs JPA server

The plain server makes you write every resource provider over your own data store. The JPA server gives you a database-backed FHIR server to configure and run. This page shows which model fits a FHIR facade over a legacy system and what each one asks of your team.

On this page

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.

FactorPlain server (you write the resource providers)JPA server (HAPI supplies the storage layer)
What you writeOne resource provider class per resource type, mapping FHIR operations to your own data sourceMostly configuration and any custom interceptors, providers or operations you need beyond the built-in resources
Where resources are storedIn whatever store you connect, such as your own database, an existing clinical system or filesIn a relational database accessed through the JPA API
Supported databasesDetermined by your own data access code, so there is no HAPI database list to rely onEmbedded H2 by default; PostgreSQL and Microsoft SQL Server for production; MySQL is not supported
Deployment optionsAny Java servlet setup you build and run around your codeDocker image, Docker Compose, Spring Boot, WAR on Tomcat, Jetty or Wildfly, or Kubernetes with a Helm chart
PartitioningNot provided; you design any tenant or region separation in your own storeResources placed in partitions for multitenancy, separating data sources or grouping by region
Terminology servicesNot provided by default; you must implement any code system lookups you needIncluded as part of the JPA server's documented feature set
ValidationNot provided by default; you decide how and whether to validate payloadsIncluded as part of the JPA server's documented feature set
MDMNot provided; you would build any patient matching or merge logicIncluded as part of the JPA server's documented feature set
FHIR version coverageConfirm in HAPI's plain server documentation which FHIR versions your chosen approach supportsDSTU2, 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.

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 areaPlain server (your own data store)JPA server (HAPI-managed database layer)
Upgrades to HAPI FHIRYour 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 restoreYour 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 migrationsSchema 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 administrationYour 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 alertingYour 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 systemYour 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 needsJava 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.

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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.

  7. 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.

Sources and further reading

Talk to our team.

Tell us what you're working on, whether it's a deployment, an audit, a security test or a cyber range. You'll speak with an engineer who can help you scope it.

  • 30-minute call: free, with no obligation.
  • NDA on request: we can sign before you share details.
  • Clear next steps: a scope and plan after the call.