Kotlin FHIRPath is an implementation of HL7® FHIR®'s FHIRPath on Kotlin Multiplatform.
Warning: The library is in alpha and subject to change. Use at your own risk.
- Strict conformation to the FHIRPath specification, with predictable and well-documented behavior
- Built with an ANTLR-generated parser for adherence to the formal grammar
- Support for validation, conversion, and comparison between compatible UCUM units
- Multiplatform support across Android, iOS, Desktop (JVM), Server-side (JVM), and Web (Wasm/JS)
- Support for FHIR R4, R4B, R5, and future versions
- Tested against the official FHIR test cases to guarantee correctness
The implementation is based on the FHIRPath Normative Release. However, we also incorporate some of the latest features and clarifications from the Continuous Build wherever feasible. Please note the experimental nature of the sections marked as STU (Standard for Trial Use) in the Continuous Build.
The library supports FHIR R4, R4B and R5. Support will be added for future FHIR versions.
This project uses ANTLR Kotlin to generate the lexer, parser and visitor directly from the formal FHIRPath grammar. This automated approach ensures correctness, improves maintainability, and significantly reduces development time.
The FHIRPath Evaluator implements the visitor class generated by ANTLR, evaluating FHIRPath expressions by traversing the in-memory data model from the Kotlin FHIR library.
A key requirement for FHIRPath evaluation is the capability to access data elements by name. To
achieve this with cross-platform compatibility (avoiding reflection), a codegen embedded in
buildSrc generates helper functions to the Kotlin FHIR data model.
graph LR
A[formal FHIRPath grammar] -- ANTLR Kotlin --> B(lexer, parser, visitor)
C(Kotlin FHIR data model<br>com.google.fhir:fhir-model)
subgraph buildSrc
direction LRTB
D[FHIR spec<br>in JSON] -- kotlinx.serialization --> E(instances of<br>StructureDefinition<br>Kotlin data class<br>)
E -- KotlinPoet --> F(generated data model helper functions)
G[ucum-essence.xml] --> H(generated UCUM helper functions)
end
B --> I(FHIRPath Evaluator)
C --> I
F --> I
H --> I
Figure 1: Architecture diagram
The following table lists the chosen internal types for the FHIRPath primitive types.
This project defines Date, DateTime, Time, and Quantity classes in order to implement the FHIRPath specification across different FHIR versions. In particular, DateTime and Time in FHIRPath may include partial time (e.g. missing minutes and seconds), which is not allowed in FHIR. Therefore, new implementations are needed.
This FHIRPath implementation adopts a strict, safety-first approach to date time comparisons, especially around the handling of timezones and date time values with different precisions.
The FHIRPath specification allows implementations to provide a default timezone offset for date time values that do not have one. See the relevant sections on equality, equivalence, and comparison.
To prioritise safety and correctness, when comparing date time values without a timezone offset with date time values with a timezone offset, this implementation does not assume a default timezone offset (such as UTC or the system's timezone offset). This is because the data could have originated from a different system or context unknown to this implementation, making any "guess" potentially incorrect and unsafe.
This leads to the following behavior:
- Equality (
=,!=) and comparison (<=,<,>,>=) operators will return an empty result{}to indicate uncertainty - Equivalence (
~) operator will returnfalsesince equivalence cannot be proven. Likewise,!~will returntrue.
@2025-01-01T00:00:00.0+00:00 = @2025-01-01T00:00:00.0 // returns {}
@2025-01-01T00:00:00.0+00:00 ~ @2025-01-01T00:00:00.0 // returns false
@2025-01-01T00:00:00.0+00:00 > @2025-01-01T00:00:00.0 // returns {}
Note: While comparing two date time values without timezone offset, the implementation will treat them as if they had the same timezone offset. This compromise is made so that local date time values can be compared:
@2025-01-01T00:00:00.0 = @2025-01-01T00:00:00.0` // returns true
According to the specification, two date time values should be compared at each precision, starting
from years all the way to seconds. However, this becomes problematic when the date time values at
hourly precision have half-hour or quarter-hour timezone offsets. Consider @2025-01-01T00+05:30
and @2025-01-01T00+05:45. In no timezone can both values still be represented as partial date time
values at the same precision in order to carry out the comparison algorithm.
Whilst it is possible to implement precision based timing in CQL using intervals, it is not part of the FHIRPath specification. For simplicity, this implementation returns an empty result for comparing partial date time values with timezone offsets.
// Indian Standard Time (IST) and Nepal Time (NPT)
@2025-01-01T00+05:30 = @2025-01-01T00+05:45 // returns {}
The FHIRPath specification does not specify the desired behavior when type checking errors occur, allowing the implementation to adopt a strict (e.g. throws an exception) or a lenient (e.g. returns an empty collection) approach. However, the official test suite include test cases that require lenient type checking. To accommodate such cases, this implementation returns an empty collection when the FHIRPath expression attempts to access a data element that does not exist.
Test failures against the official FHIR test cases are documented in the table below.
| Test case | Root cause | STU | Tracking issue / PR | Note |
|---|---|---|---|---|
testPolymorphismAsB |
Test | To be raised | No error should be thrown according to specification. | |
testDateTimeGreaterThanDate1 |
Implementation | Comparison of two date time values, one with a timezone offset one without; see Date time values without timezone offset | ||
testStringIntegerLiteralToQuantity |
Specification/Test | Discussion | ||
testQuantityLiteralWkToString |
Specification/Test | As above. | ||
testQuantityLiteralWeekToString |
Specification/Test | As above. | ||
testQuantity4 |
Test | PR | ||
testSubSetOf3 |
Specification/Test | The test resource is invalid and missing (FHIR/fhir-test-cases#247); the scope of "$this" is unclear (https://jira.hl7.org/browse/FHIR-44601) | ||
testIif11 |
Implementation | https://jira.hl7.org/browse/FHIR-44774; https://jira.hl7.org/browse/FHIR-44601 | ||
testEncode* |
Implementation | STU | Function encode is not implemented. |
|
testDecode* |
Implementation | STU | Function decode is not implemented. |
|
testEscape* |
Implementation | STU | Function escape is not implemented. |
|
testUnescape* |
Implementation | STU | Function unescape is not implemented. |
|
testTrace* |
Implementation | Function trace is not implemented. |
||
testNow1 |
Specification/Test | As testDateTimeGreaterThanDate1. |
||
testSort* |
Specification/Test | Function sort is not defined in the specification. |
||
testPlusDate13 |
Specification/Test | https://chat.fhir.org/#narrow/channel/179266-fhirpath/topic/Definite.20durations.20above.20seconds.20in.20date.20time.20arithmetic/with/564095766 | ||
testPlusDate15 |
Specification/Test | As above. | ||
testPlusDate18 |
Implementation | To be fixed together with testPlusDate13, testPlusDate15, testPlusDate21, testPlusDate22 for a consistent implementation. |
||
testPlusDate19 |
Implementation | To be fixed together with testPlusDate13, testPlusDate15, testPlusDate21, testPlusDate22 for a consistent implementation. |
||
testPlusDate20 |
Implementation | To be fixed together with testPlusDate13, testPlusDate15, testPlusDate21, testPlusDate22 for a consistent implementation. |
||
testPlusDate21 |
Specification/Test | As testPlusDate13. |
||
testPlusDate22 |
Specification/Test | As testPlusDate13. |
||
testMinus5 |
Specification/Test | As testPlusDate13. |
||
testPrecedence3 |
Specification/Test | FHIR/fhir-test-cases#249 | https://chat.fhir.org/#narrow/channel/179266-fhirpath/topic/FHIRPath.20test.20suite.20for.20precedence.20correct.3F/with/564497251 | |
testPrecedence4 |
Specification/Test | FHIR/fhir-test-cases#249 | https://chat.fhir.org/#narrow/channel/179266-fhirpath/topic/FHIRPath.20test.20suite.20for.20precedence.20correct.3F/with/564497251 | |
testVariables* |
Implementation | Variables are not implemented. | ||
testExtension* |
Implementation | Function extension is not implemented. |
||
testType* |
Implementation | Function type is not implemented. |
||
testConformsTo* |
Implementation | Function conformsTo is not implemented. |
||
LowBoundary* |
Implementation | STU | Function lowBoundary is not implemented. |
|
HighBoundary* |
Implementation | STU | Function highBoundary is not implemented. |
|
Comparable* |
Implementation | Function comparable is not implemented. |
||
Precision* |
Implementation | Function precision is not implemented. |
||
testIndex |
Implementation | $index is not implemented. |
||
testPeriodInvariantOld |
Implementation | Function hasValue is not implemented. |
||
testPeriodInvariantNew |
Implementation | Function lowBoundary and function highBoundary are not implemented. |
||
testFHIRPathIsFunction* |
Implementation | |||
testFHIRPathAsFunction* |
Implementation | |||
testContainedId |
Implementation | |||
testCombine2 |
Implementation | FHIR String and Kotlin String comparison issue in exclude() function. |
||
testCombine3 |
Implementation | As above. |
The root cause column documents if the test failure is caused by implementation issues in this repository, if the test cases themselves are problematic, or it is believed that the specification itself is ambiguous or inconsistent. For issues in the test cases and the specification, discussions and proposals should be linked in the table above.
To use Kotlin FHIRPath, add it to the dependencies in your project. To do that, first make sure to
include the Google Maven repository in the build.gradle.kts file in
your project root.
// build.gradle.kts
repositories {
// Other repositories such as mavenCentral() and gradlePluginPortal()
google()
}
Next, follow the instructions for your specific project type.
For Kotlin Multiplatform projects, add the dependency to the shared commonMain source set within
the kotlin block of the module's build.gradle.kts file (e.g., composeApp/build.gradle.kts or
shared/build.gradle.kts). This makes the library available across all platforms in your project.
// e.g., composeApp/build.gradle.kts or shared/build.gradle.kts
kotlin {
sourceSets {
commonMain.dependencies {
implementation("com.google.fhir:fhir-path:1.0.0-alpha02")
}
}
}
For Android projects, add the dependency to the dependency block in the module's
build.gradle.kts file (e.g., app/build.gradle.kts).
// e.g., app/build.gradle.kts
dependencies {
implementation("com.google.fhir:fhir-path:1.0.0-alpha02")
}
To evaluate a FHIRPath expression, create a FhirPathEngine for the correct FHIR version and use
evaluateExpression function:
import com.google.fhir.fhirpath.FhirPathEngine
import com.google.fhir.model.r4.FhirR4Json
val patientExampleJson = ... // Load "patient-example.json"
val patient = FhirR4Json().decodeFromString(patientExampleJson)
val fhirPathEngine = FhirPathEngine.forR4()
val results = fhirPathEngine.evaluateExpression("name.given", patient) // ["Peter", "James", "Jim", "Peter", "James"]
To generate the lexer, parser, and visitor locally using ANTLR Kotlin:
./gradlew generateKotlinGrammarSourceThe generated code will be placed in fhir-path/build/generated under package
com.google.fhir.fhirpath.parsers.
To run the model extension codegen in buildSrc locally:
./gradlew generateR4Helpers
./gradlew generateR4BHelpers
./gradlew generateR5HelpersThe generated code will be located in fhir-path/build/generated under packages
com.google.fhir.model.<FHIR_VERSION>.ext and com.google.fhir.fhirpath.
To run the UCUM helper codegen in buildSrc locally:
./gradlew generateUcumHelpersThe generated code will be located in fhir-path/build/generated under package
com.google.fhir.fhirpath.ucum.
Dependencies must be kept in sync between the
buildSrc/build.gradle.kts file and the
gradle/libs.versions.toml file. The former cannot use the latter
since the buildSrc directory is precompiled separately in Gradle.
XmlUtil is used to load the XML test cases from the
third_party directory. To run the tests:
./gradlew :fhir-path:jvmTestThe number of passing test cases is displayed on a badge at the top of this page.
To create a maven repository from the generated FHIR model, run:
./gradlew :fhir-path:publish
This will create a maven repository in the fhir-path/build/repo directory with artifacts for all
supported platforms.
To zip the repository, run:
./gradlew :fhir-path:zipRepo
This will generate a .zip file in the fhir-path/build/repoZip directory.
The third_party directory includes resources from the FHIRPath specification and related repositories for code generation and testing purposes:
fhir-test-cases: content from the fhir-test-cases repotests-fhir-r4.xml: R4 test cases (commit)resourcesJSON versions of the relevant test resources generated using Anton V.'s FHIR Converter alongside the XML versions (commit). The XML and JSON resource files in the fhir-test-cases repository are inconsistent; we use XML files converted to JSON.
fhirpath-2.0.0: the formal antlr grammar from the FHIRPath Normative Release N1 (v2.0.0) includinghl7.fhir.r4.core: content from FHIR R4 for code generationhl7.fhir.r4b.core: content from FHIR R4B for code generationhl7.fhir.r5.core: content from FHIR R5 for code generationucum: content from the UCUM repo
This is not an officially supported Google product. This project is not eligible for the Google Open Source Software Vulnerability Rewards Program.

