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.
- Built with an ANTLR-generated parser for strict adherence to the formal FHIRPath grammar
- Conforms strictly to the specification, with predictable and well-documented behavior
- Support for validation, conversion, and comparison between compatible UCUM units
- Designed for portability, providing a single engine across JVM, Android, iOS, Web (JS), and Native platforms
- 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 currently supports R4 only. Support for other versions will be added as the library matures.
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.
(*): Classes defined in Kotlin FHIR (**): Classes defined in this project
Classes from the Kotlin FHIR are used for more complex types that do not have direct representations in Kotlin. For DateTime and Time, the requirements in FHIRPath are more lenient than in the FHIR specification. As a result, custom classes need to be authored to handle cases where the minutes, seconds, or milliseconds are not present (allowed in FHIRPath but not allowed in FHIR).
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 {}
Due to the library's WIP status, not all test cases from the published official test suites are passing. The failures 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. | |
testLiteralDecimalGreaterThanNonZeroTrue |
Implementation | #7 | ||
testLiteralDecimalGreaterThanZeroTrue |
Implementation | #7 | ||
testLiteralDecimalGreaterThanIntegerTrue |
Implementation | #7 | ||
testLiteralDecimalLessThanInteger |
Implementation | #7 | ||
testDateTimeGreaterThanDate1 |
Specification/Test | To be raised | Unclear in the specification whether the result should still be empty if two values have different precisions but the comparison can still be "certain" (e.g. 2025 is greater than 2024-01). | |
testDecimalLiteralToInteger |
Test | To be raised | The result should be true. | |
testStringIntegerLiteralToQuantity |
Specification/Test | Discussion | ||
testQuantityLiteralWkToString |
Specification/Test | As above. | ||
testQuantityLiteralWeekToString |
Specification/Test | As above. | ||
testQuantity4 |
Test | PR | ||
testExists3 |
Implementation | Equality of enum and string is not implemented. | ||
testSubSetOf3 |
Implementation | |||
testQuantity9 |
Implementation | Quantity multiplication is not implemented. | ||
testQuantity10 |
Implementation | Quantity division is not implemented. | ||
testQuantity11 |
Implementation | As above. | ||
testDistinct2 |
Implementation | Function descendants is not implemented. |
||
testDistinct3 |
Implementation | As above. | ||
testDistinct5 |
Implementation | As above. | ||
testDistinct6 |
Implementation | As above. | ||
testSelect3 |
Implementation | |||
testRepeat* |
Implementation | Function repeat is not implemented. |
||
testAggregate* |
Implementation | Function aggregate is not implemented. |
||
testIif10 |
Implementation | |||
testIif11 |
Implementation | |||
testMatchesSingleLineMode1 |
Implementation | |||
testReplace5 |
Implementation | |||
testReplace6 |
Implementation | |||
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. |
|
testTrace2 |
Implementation | |||
testNow1 |
Specification/Test | As testDateTimeGreaterThanDate1. |
||
testSort* |
Specification/Test | Function sort is not defined in the specification. |
||
testEquality28 |
Implementation | |||
testNEquality24 |
Implementation | |||
testEquivalent11 |
Implementation | |||
testEquivalent22 |
Implementation | |||
testLessThan22 |
Implementation | |||
testLessThan24 |
Implementation | |||
testLessOrEqual22 |
Implementation | |||
testLessOrEqual24 |
Implementation | |||
testGreaterOrEqual22 |
Implementation | |||
testGreaterOrEqual24 |
Implementation | |||
testGreaterThan22 |
Implementation | |||
testGreaterThan24 |
Implementation | |||
testGreaterThan6 |
Implementation | |||
testCombine* |
Implementation | Function combine is not implemented. |
||
testPlusDate* |
Implementation | Date time arithmetic not implemented. | ||
testMinus3 |
Implementation | |||
testMinus5 |
Implementation | |||
testMod4 |
Implementation | |||
testAbs3 |
Implementation | |||
testPrecedence3 |
Implementation | |||
testPrecedence4 |
Implementation | |||
testPrecedence6 |
Implementation | |||
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. |
||
testLowBoundary* |
Implementation | STU | Function testLowBoundary is not implemented. |
|
testHighBoundary* |
Implementation | STU | Function testHighBoundary is not implemented. |
|
testComparable* |
Implementation | Function testComparable is not implemented. |
||
testPrecision* |
Implementation | Function precision is not implemented. |
||
testIndex |
Implementation | |||
testPeriod* |
Implementation | |||
testFHIRPathIsFunction* |
Implementation | |||
testFHIRPathAsFunction* |
Implementation | |||
testContainedId |
Implementation |
The root cause column documents if the test failure is caused by issues with the implementation (this repository), the tests, the specification itself, or is under investigation. We exclude test cases that would fail due to issues in the tests and the specification itself. But we track the ongoing discussions and resolutions in this table.
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 generateR4HelpersThe generated code will be located in fhir-path/build/generated under packages
com.google.fhir.fhirpath and com.google.fhir.fhirpath.ext.
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)
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 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.

