There are several FHIR terminology server products available today, both free and commercial. In my country, there have been ongoing discussions about which product we should invest in. Over the past week, I spent time exploring three terminology servers and have summarized my findings in this blog post.
Special thanks to AeHIN for allowing me to test Ontoserver with their evaluation license.
For this round, I tested only three products. However, if other vendors are interested in having their servers evaluated, I would be happy to test and include them as well.
Featured image adapted from the work of Paul Schafer on Unsplash
Test Setup
I conducted the tests between April 20 and 27, 2025, using the following server versions:
- HAPI FHIR version 7.6.0
- Snowstorm version 10.7.0
- Ontoserver version 6.22.5
I focused on three CodeSystems: SNOMED CT, LOINC, and TMT. SNOMED CT and LOINC are well-known international standards with structures more complex than a simple flat list of codes, making them ideal for testing server capabilities.
Thai Medicines Terminology (TMT) served as a representative of a custom CodeSystem. I structured it as a flat list but added properties to each concept. I believe that most CodeSystems used in healthcare will fall into this category.
The FHIR community is actively developing the FHIR Terminology Ecosystem Implementation Guide (IG), which will make it easier in the future to test and compare server capabilities. However, for now, I decided to perform the testing manually.
In each section, you will find a summary table of my test results. To save space, I will abbreviate “SNOMED CT” as “SCT” and “LOINC” as “LNC.”
Importing & TerminologyCapabilities
| Topic | HAPI FHIR | Snowstorm | Ontoserver |
|---|---|---|---|
| Importing SCT | ✅ Via CLI | ✅ Via UI | ✅ Via UI or syndication |
| Importing LNC | ☑️ Via CLI and need customization | ✅ Via CLI | ✅ Via CLI or syndication |
| Importing TMT | ✅ POST request of the CodeSystem resource | ✅ Via CLI and need data transformation | ✅ POST request of the CodeSystem resource |
| TerminologyCapabilities | ❌ Display the CapabilityStatement instead | ❌ Not display anything | ✅ Correctly display |
Across all the products, importing terminology content is generally straightforward. However, Ontoserver stands out as the easiest, thanks to its ability to pull content directly from the source via syndication (though the latest version may not always be available this way).
For custom terminologies, you can also use the POST CodeSystem operation in Ontoserver. In contrast, Snowstorm requires you to first transform the content to match its specific format before importing via the CLI. It would be easier if Snowstorm supported importing with POST CodeSystem as well.
HAPI FHIR also handles terminology importing well. The main challenge lies with importing LOINC: you need to manually edit the loincupload.properties file beforehand. This process isn’t very straightforward and often requires some online searching to figure out.
When it comes to TerminologyCapabilities, Ontoserver appears to be the only product that supports this feature.
CodeSystem Operations
| Topic | HAPI FHIR | Snowstorm | Ontoserver |
|---|---|---|---|
| $lookup SCT | ☑️ Can do, but with less detail. | ✅ Good | ✅ Good |
| $lookup LNC, TMT | ✅ Good | ✅ Good | ✅ Good |
| CodeSystem $validate-code | ☑️ Can do, but with less detail. | ✅ Good | ✅ Good |
| $subsumes SCT | ✅ Good | ✅ Good | ✅ Good |
| $subsumes LNC | ✅ Good | ✅ Good | ✅ Good |
| $subsumes TMT by using property | ❌ Not supported | ✅ Good | ✅ Good |
| $find-matches SCT, LNC, TMT | ❌ Not supported | ❌ Not supported | ✅ Good |
$lookup and $validate-code
All three terminology servers perform well with $lookup and $validate-code. One small caveat: HAPI FHIR displays much less information compared to Ontoserver and Snowstorm. This can be an issue if your application needs to show detailed information about each code.

$lookup in HAPI FHIR and Snowstorm$subsumes
All three servers handle $subsumes correctly for LOINC and SNOMED CT. However, for TMT, I defined the child-parent relationships using CodeSystem.property. In this setup, HAPI FHIR cannot infer the hierarchy, while Snowstorm and Ontoserver can.
I haven’t tested a more straightforward approach yet, such as nesting child concepts directly under their parent concepts. I believe that, with such structure, HAPI FHIR should be able to infer subsumption properly.

concept.property. Snowstorm and Ontoserver can use these properties to infer subtype relationships, but HAPI FHIR cannot.$find-matches
Among the three products, only Ontoserver currently supports the $find-matches operation.
ValueSet Operations
| Topic | HAPI FHIR | Snowstorm | Ontoserver |
|---|---|---|---|
| $expand SCT ECL | ❌ Not supported | ✅ Good | ✅ Good |
| $expand SCT ECL and filter with “pneu pneu” | ❌ Not supported | ☑️ Can do, but the result could be better | ✅ Good |
| $expand SCT using information from CodeSystem supplement | ❌ Not supported | ❌ Not supported | ✅ Good |
| $expand LNC (part ValueSet) | ❌ Not supported | ❌ Not supported | ✅ Good |
| $expand LNC (other ValueSet) | ✅ Good | ✅ Good | ✅ Good |
$expand of SNOMED CT
When it comes to expanding SNOMED CT, I think most users would expect for expansion using SNOMED ECL. Both Snowstorm and Ontoserver support this well, but HAPI FHIR does not.
If you look more closely at the results, however, you’ll notice that Ontoserver delivers slightly better quality. For example, try filtering with the keyword "pneu pneu". The expected behavior is for “Pneumococcal pneumonia” to appear first in the results. Ontoserver handles this perfectly.
In contrast, Snowstorm does return relevant matches, but it ranks ordinary “Pneumonia” first—not incorrect, but not as optimal as Ontoserver.

$expand of SNOMED CT using CodeSystem Supplement
Ontoserver is the only server among the three that supports expansion using a CodeSystem supplement.
Snowstorm doesn’t allow POST operations on CodeSystem resources, making it impossible to create a supplement.
HAPI FHIR doesn’t support SNOMED ECL natively and doesn’t support its supplement using this approach. However, I haven’t yet tested this feature with custom terminologies.

$expand of LOINC
When you import LOINC into a terminology server, it usually generates many LOINC ValueSets automatically, allowing you to use the ValueSets defined in the official LOINC specifications. Ontoserver handles this perfectly.
However, for some reason, HAPI FHIR and Snowstorm do not create ValueSets for LOINC Parts. Instead, they only generate ValueSets for LOINC Answer Lists, LOINC Groups, and five key ValueSets: loinc-all, loinc-document-ontology, loinc-imaging-document-codes, loinc-rsna-radiology-playbook and loinc-universal-order-set



$expand of TMT
| Topic | HAPI FHIR | Snowstorm | Ontoserver |
|---|---|---|---|
| $expand TMT using extensional ValueSet | ✅ Good | ✅ Good | ✅ Good |
| $expand TMT using intensional ValueSet with ‘concept’ property | ❌ Not supported | ✅ Good | ✅ Good |
| $expand TMT using intensional ValueSet with other properties | ❌ Not supported | ❌ Not supported | ✅ Good |
| $expand TMT using intensional ValueSet posted as a ValueSet in Parameter resource | ❌ Not supported | ☑️ Good (depend on ValueSet definition) | ✅ Good |
| $expand TMT using an intensional ValueSet posted as a ValueSet in in Parameter resource with a regular expression filter | ❌ Not supported | ❌ Not supported | ✅ Good |
All three servers can perform $expand on extensional ValueSets (where all codes are explicitly listed).
However, HAPI FHIR cannot handle intensional ValueSets (where codes are defined by criteria and the server generates the list automatically).
When it comes to intensional ValueSets:
- Ontoserver fully supports all methods of defining ValueSets as specified in the FHIR specification.
- Snowstorm supports only the use of the
conceptproperty, and only with theis-aanddescendent-ofoperations. If you want to use other filters, you must create an extensional ValueSet instead.
Both Snowstorm and Ontoserver support expanding a ValueSet defined on-the-fly by POSTing a ValueSet resource inside a Parameters resource.
This method allows fairly complex filtering. However, because Snowstorm only supports a limited set of properties and operations, its capabilities here are somewhat restricted.
One pleasant surprise was that Ontoserver supports filtering with regular expressions. I hadn’t expected this, but it worked impressively well.

concept property in Snowstorm.

$validate-code
| Topic | HAPI FHIR | Snowstorm | Ontoserver |
|---|---|---|---|
| ValueSet $validate-code SCT with implicit ValueSet | ❌ Not supported | ✅ Good | ✅ Good |
| ValueSet $validate-code LNC with part ValueSet | ❌ Not supported | ❌ Not supported | ✅ Good |
| ValueSet $validate-code LNC with other ValueSet | ✅ Good | ✅ Good | ✅ Good |
| ValueSet $validate-code intensional TMT | ❌ Not supported | ☑️ Good (depend on ValueSet definition) | ✅ Good |
$validate-code within a ValueSet is straightforward — if the server can expand the ValueSet, the server can validate it.
ConceptMap Operations
| Topic | HAPI FHIR | Snowstorm | Ontoserver |
|---|---|---|---|
| $translate SCT implicit simple map and association type | ❌ Not supported | ✅ Good | ✅ Good |
| $translate SCT implicit extended map | ❌ Not supported | ✅ Good | ⁉️ It seems to be supported, but I haven’t found a way to enable it. |
| $translate LOINC | ✅ Good | ✅ Good | ☑️ Need manually POST ConceptMap. |
| $translate TMT | ✅ Good | ❌ Not supported | ✅ Good |
$translate with SNOMED CT
For SNOMED CT, $translate usually relies on implicit ConceptMaps. HAPI FHIR does not support this. Snowstorm performs best here, supporting simple maps, association type reference sets, and extended maps (such as SNOMED CT to ICD-10 mappings). I haven’t yet tested other types of reference sets, but I guess it supports too.
Ontoserver supports simple maps and association type reference sets. However, I’m not sure whether it supports other types. I suspect it does, since their documentation mentions support for the Australian Substance to International Substance map, which is neither a simple map nor an association type. That said, I haven’t figured out how to configure it for using it with other reference set types.
To be fair, according to the FHIR specification, implicit ConceptMaps are only formally defined for simple maps and association type reference sets. So even if Ontoserver doesn’t support extended maps, it is still fully compliant with the FHIR standard.
As an alternative, you can convert an extended map into a FHIR ConceptMap and POST it manually to Ontoserver. However, this process is less convenient compared to Snowstorm, which handles it more directly.

$translate to ICD-10 using Snowstorm and Ontoserver.$translate with LOINC
When importing LOINC into HAPI FHIR and Snowstorm, many ConceptMap resources are generated automatically. This allows you to use the mappings specified in the LOINC specification without any issues.
Ontoserver, on the other hand, does not automatically generate these ConceptMaps. You must manually obtain the ConceptMaps from LOINC and POST them into the server, which makes the process less convenient.

$translate from LOINC to SNOMED CT in Snowstorm and Ontoserver.$translate with TMT
For TMT, $translate is the most straightforward: if you can POST the ConceptMap resource, you can immediately perform translations.
However, Snowstorm does not allow posting custom ConceptMaps, meaning you cannot use $translate for custom terminologies there.
$closure
I haven’t tested this operation yet, but I think Ontoserver supports it, as it’s clearly documented and has been mentioned multiple times in their presentations.
Other Considerations
- These tests were performed only using the FHIR Terminology Service API.
However, if you explore Snowstorm’s native API, you’ll find it offers a much broader range of SNOMED CT functionality—far beyond what is defined in the FHIR Terminology Service specification. For example, it allows you to retrieve Description IDs and Relationship IDs, which are not covered by standard FHIR operations. - Ontoserver also provides many extended features that go beyond FHIR. Several of them are quite impressive, such as the algorithmic ConceptMap generation.
Summary
- Overall, Ontoserver offers the most comprehensive capabilities. It supports virtually everything specified in the FHIR standards. However, for use in Thailand, one challenge remains: handling SNOMED CT to ICD-10 mappings. But I think there are several ways to address this..
- For an open-source and free solution, Snowstorm performs remarkably well and covers many essential terminology use cases. If your terminology needs are not too complex, Snowstorm is a strong, practical option worth considering.
- While HAPI FHIR is an excellent FHIR reference implementation, it may not be the best choice if you’re looking for a full-featured FHIR terminology server.

Leave a Reply