If you've ever written FHIR R4 by hand, you know the loop: open a Patient resource, write some JSON, paste it into Inferno or HL7's online validator, fix the errors it surfaces, paste back. Repeat for every Observation, Bundle, Encounter you touch. Open another tab for FHIRPath. Open another for code-system lookups. The IDE doesn't know FHIR exists. Your validator doesn't know about your IDE. The tooling stops at the file boundary.
This is the wrong workflow for IDE-first development.
So I built FHIR Toolkit, a free, open-source JetBrains plugin that brings FHIR R4 tooling directly into IntelliJ, PyCharm, WebStorm, GoLand — anywhere you write JSON for healthcare integrations. v0.1 shipped to the JetBrains Marketplace on May 11, and v0.2 followed four days later with five more features.
This post covers everything that's in it today, why each piece exists, and a few implementation notes that might save the next plugin developer some time.
What v0.1 shipped — the validator base
Five features, all client-side, all designed to remove that copy-paste loop:
1. Inline FHIR JSON validation
Open any .json file containing a FHIR resource (recognized by a top-level resourceType field), and the HAPI FHIR R4 validator runs against the bundled R4 StructureDefinitions. Errors and warnings appear inline as you type — same UX as a TypeScript or Kotlin error. Cardinality issues, malformed datatypes, invalid coded values — all surfaced where you'd expect them.
2. Sample resource generator
Tools → Insert FHIR Resource… (or right-click in any JSON file) opens a chooser popup for Patient, Observation, Encounter, Bundle, Practitioner, or Organization. Each skeleton inserts at the cursor and is locked in by an integration test that asserts zero validation errors. No more copying templates from the spec.
3. Datatype hover documentation
Hover on a property key inside a recognized FHIR datatype — coding, system, reference, value, etc. — and you get the contextual description for that field's role. Faster than opening hl7.org/fhir/R4/datatypes.html in another tab.
4. Bundle reference navigation
Inside a Bundle, click the gutter icon next to any "reference" value to jump to the matching entry's resource. Resolves relative Type/id references, exact fullUrl matches, and absolute URLs whose suffix matches. Like Cmd-click navigation but for FHIR's reference graph.
5. Curated code-system hover lookup
Hover on a code value inside a Coding or Quantity, and you get the human-readable display when the system is one of LOINC, SNOMED CT, ICD-10-CM, or UCUM. Ships with a curated starter set focused on common vital signs, chronic conditions, and units.
What v0.2 added
Four days after v0.1 went live, the install curve started flattening — useful but quiet. The fastest way to test whether the wedge was real was to ship more of what was already on the backlog. Five more features, same constraints (client-side, no telemetry, no network):
6. Resource tree tool window
A right-anchored tool window renders the active FHIR JSON as a collapsible tree. Click any node to jump to its source line. Useful for getting your bearings inside a 500-line Bundle without scroll-hunting.
7. Bundle Graph tool window
Each Bundle.entry becomes a node; its outgoing references are nested as children and marked resolved or unresolved. Reuses the same resolution rules as the gutter-icon navigator — relative Type/id, exact fullUrl, absolute-URL-with-matching-suffix. Tells you at a glance whether your Bundle is internally consistent.
8. FHIRPath playground
A third tool window with a text input and an Evaluate button, scoped to the active resource. Results render as raw values for primitives and pretty-printed JSON for resources or backbone elements. Uses HAPI's modern IFhirPath API (wrapped in the classloader helper described below).
9. Embedded mock FHIR server
Tools → Run Mock FHIR Server toggles an in-memory R4 endpoint on an auto-allocated localhost port. Backed by the JDK's built-in HttpServer — no extra dependencies — with a minimal CapabilityStatement and CRUD over the common resource types. The intended use is exercising your FHIR client without standing up HAPI JPA or hitting a public test server.
10. Synthetic clinical scenarios
Tools → Insert FHIR Scenario opens a chooser with four hand-crafted multi-resource Bundles — a diabetes encounter, a pediatric asthma exacerbation, a hypertension + lipid panel workflow, and an NSTEMI presentation. Each Bundle is pinned in CI to zero validation errors. Not a Synthea integration; Synthea ships 100+ MB of seed data, and the curated path is ~10 KB while still covering the realistic-test-data use case for IDE work.
Also in v0.2: the curated code-system tables grew from ~96 codes to ~382 across the four supported systems.
Privacy stance
Healthcare developers have a strong privacy reflex about their data, and they should — patient JSON is the kind of file that absolutely cannot end up in a third-party SaaS log. So:
- Runs entirely in the IDE.
- No telemetry. No analytics. No phone-home.
- No network calls. The validator uses bundled R4 StructureDefinitions; terminology validation runs offline against
InMemoryTerminologyServerValidationSupport. - Your data does not leave your machine.
This isn't a marketing line. It's the technical default I chose deliberately for v0 and intend to preserve for any paid tier later.
Implementation notes for the curious
A few things I learned while building this that might save another plugin developer some time:
HAPI uses Java SPI in spots that bite IDE plugins
ServiceLoader.load(X.class) defaults to the thread-context classloader. Inside the IDE, that's IntelliJ's app classloader, not your plugin's. HAPI's CacheFactory, StAX detection, and JAXB lookup all hit this. The fix is to wrap HAPI calls in a helper that swaps the context classloader to the plugin's:
fun <T> withPluginClassLoader(block: () -> T): T {
val previous = Thread.currentThread().contextClassLoader
Thread.currentThread().contextClassLoader = FhirContextHolder::class.java.classLoader
return try { block() } finally {
Thread.currentThread().contextClassLoader = previous
}
}
Every HAPI entry point in the plugin — parser, validator, FHIRPath engine, mock server bootstrap — goes through this helper.
org.ogce:xpp3 (a transitive HAPI dep) breaks the IDE's PluginClassLoader
It bundles a duplicate javax.xml.namespace.QName that clashes with the JDK's java.xml module. Excluding it via Gradle fixes the load error — Jackson and Woodstox handle FHIR's JSON and XML paths cleanly without it:
configurations.all {
exclude(group = "org.ogce", module = "xpp3")
}
HAPI 8.x's LenientErrorHandler is not actually lenient about coded values
The default throws on the first invalid enum (e.g., gender: "notagender") and never reaches the validator. Calling setErrorOnInvalidValue(false) makes parsing keep going and lets the validator emit a SingleValidationMessage for every issue, not just the first. Without this, a file with three problems shows one underline; with it, you see all three.
SingleValidationMessage.locationLine / locationCol are unusable for IDE annotation
HAPI re-serializes the resource to a single line before validating and reports columns in that serialization. Every message comes back with locationLine=1. The reliable signal is locationString — a FHIRPath like Patient.gender or Bundle.entry[0].resource.id — which you walk through the JSON PSI tree to find the right node. Cardinality errors (where the field genuinely doesn't exist in the source) gracefully fall back to anchoring on the resourceType property so they remain visible.
Offline terminology will warn loudly unless you filter
The bundled terminology service doesn't know LOINC, SNOMED, ICD-10-CM, or RxNorm codes — it produces an "unknown CodeSystem" warning on every Coding referencing those systems. In an FHIR file that's a lot of noise. The annotator strips these specific warnings (and a couple of related ones) so the inspector pane shows real problems, not infrastructure complaints.
What's next
US Core profile validation is the obvious next step — that's the real money pain for anyone working on ONC compliance, and it's what the v0 base was built to support. Bulk project validation and developer-friendly error explanations are also on the list. The shape of that release depends on feedback from this one; right now I'm reading every GitHub issue and watching which features get reached for.
Try it
- JetBrains Marketplace: https://plugins.jetbrains.com/plugin/31676-fhir-toolkit
- Source: https://github.com/StockGorilla/fhir-toolkit
- License: Apache 2.0
Free. No telemetry. No network calls. Runs in your IDE.
Feedback welcome via GitHub Issues — particularly: which feature is most useful to you, what's missing, what's broken. I read every issue.

Top comments (0)