P99 latency is the metric a clinician feels first. A FHIR server that posts a beautiful average response time but pegs the 99th percentile above a second will still produce that one in a hundred user click that visibly stalls inside an EHR workflow. A new public benchmark from Health Samurai reports per-operation P99 numbers across four FHIR servers under identical conditions, and the spread is the kind of detail US EMR teams should read carefully before any procurement.
The benchmark runs Aidbox, HAPI FHIR, Medplum, and the Microsoft FHIR Server on the same bare-metal host with the same Synthea dataset of 1,000 patients, with the snapshot dated 2026-06-29. For more on FHIR for US healthcare teams, the broader hub covers the surrounding topics.
The Full CRUD P99 Table
The benchmark reports P99 in milliseconds per operation, across 300 concurrent threads on nine resource types. The published snapshot looks like this:
| Server | Create P99 | Read P99 | Update P99 | Delete P99 |
|---|---|---|---|---|
| Aidbox | 106 ms | 91 ms | 110 ms | 93 ms |
| HAPI FHIR | 276 ms | 225 ms | 271 ms | 239 ms |
| Medplum | 758 ms | 404 ms | 626 ms | 647 ms |
| Microsoft FHIR Server | 1,180 ms | 379 ms | 1,233 ms | 1,064 ms |
Four servers, four very different tail-latency profiles. The numbers come straight from the benchmark report. Health Samurai authored the benchmark and also makes Aidbox; the value of the open repo is that the harness is auditable.
Why P99 Beats the Average
Averages flatter every FHIR server. A server that handles 99 requests in 80 ms and one request in 4 seconds shows an average that looks fine on a slide deck. The user behind that one slow request, though, sees the stall in the chart, and the support ticket lands the next morning. P99 is the metric that translates most directly into the SLO a US health system writes against an EHR experience.
A practical rule: any P99 above 500 ms on a write operation should be visible to product owners during scoping. Anything above 1,000 ms means the workflow needs either retry logic or a different server.
Where the Tail Hides on Each Server
Aidbox lands in the 90 to 110 ms range across all four CRUD operations. That kind of symmetry suggests the server is not blocking on a particular operation type. HAPI FHIR sits in the 225 to 276 ms band, also fairly symmetric, with the reference JPA-backed PostgreSQL implementation behaving the way most experienced HAPI operators expect.
Medplum's P99 widens noticeably on writes. 758 ms on create and 626 ms on update are tail numbers that show up in user-facing forms during peak load. The benchmark's per-container resource shape (Medplum runs 8 replicas of 1 vCPU and 3 GB each, per its documented deployment) is part of that picture.
The Microsoft FHIR Server is where the most interesting variance shows up. Read P99 at 379 ms lands in the same neighborhood as Medplum's. Create and update P99 sit above one second (1,180 ms and 1,233 ms). On the search side, the report flags Microsoft's quantity P99 at roughly 1.2 seconds and composite P99 at roughly 1.9 seconds, with the note that Medplum does not support composite search at all.
What This Means for a US EMR Build
The truth is that CRUD P99 is the FHIR-server number that most often shapes the EHR user experience. A team picking a backend for a US outpatient EMR is choosing what the slowest one-in-a-hundred chart save will feel like. For an adjacent comparison framing, the HAPI vs Medplum write-up for US EMR modernization covers the open-source side, and the top 5 FHIR servers for US outpatient EMR modernization in 2026 covers the broader shortlist.
A Measured Read of These Numbers
The catch is that this benchmark uses a 1,000-patient Synthea dataset that fits in memory, with empty database baselines and a clean warm-up. P99 tends to grow as the underlying database fills and as the working set leaves RAM. The next post in the series is expected to test at scale. Read the current snapshot as the floor of P99 each server can achieve on a clean workload, not as the ceiling of what production will feel like.
The useful move is to fork the open harness, swap in a representative slice of your own resource graph, and rerun. The P99 spread under your own data shape is the number that matters.
