# 🗃️ FHIR2 3.0.0 Data Access

**URL:** <https://talk.openmrs.org/t/fhir2-3-0-0-data-access/47435>\
**Category:** FHIR\
**Tags:** developers\
**Created:** [November 12, 2025, 7:58pm UTC](https://talk.openmrs.org/t/fhir2-3-0-0-data-access/47435 "2025-11-12T19:58:02Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![ibacher](https://talk.openmrs.org/user_avatar/talk.openmrs.org/ibacher/32/19751_2.png) [@ibacher](https://talk.openmrs.org/u/ibacher)\
**Post date:** [November 12, 2025, 7:58pm UTC](https://talk.openmrs.org/t/fhir2-3-0-0-data-access/47435/1 "2025-11-12T19:58:02Z")

</div>

So, I recently made a post talking about [the future of the FHIR2 module](https://talk.openmrs.org/t/future-major-versions-of-the-fhir2-module/47332), including introducing the concept of the FHIR2 3.0.0. The overall architecture of the FHIR2 module remains [exactly as articulated here](https://github.com/openmrs/openmrs-module-fhir2/blob/6fc8921d1ac40c4ba7018a71292998b1d4c57f31/README.md#development-principles) and as much as possible the pubic API for things remains the same. However, we have had to introduce changes to the internal API that will invariably be breaking for anyone who has used our tooling.

The intention behind our DAO layer remains to the same, to try to make support for new data access classes as simple and uniform as possible. Basically, for most DAO implementations, the intention was that you just had to extend `BaseFhirDaoImpl` and override hopefully no more than two methods:

```java
class FhirExampleDaoImpl extends BaseFhirDao<OpenmrsObject> implements FhirOpenmrsObjectDao {
    @Override
    protected void setupSearchParams(Criteria criteria, SearchParameterMap theParams) {
       // use the criteria object to build a Hibernate Criteria query that matches the `SearchParameterMap`
    }

   @Override
   protected String paramToProp(String param) {
      // take the name of the parameter (passed as the _sort param in the REST API)
      // return the corresponding name of the Hibernate column to use in the ORDER BY
   }
}

```

With FHIR2 3.x our big change is that we’re no longer supporting the Hibernate Criteria API and this is because Hibernate itself drops support for this API in Hibernate 6 onwards. Instead this is replaced with the more standardized JPA Criteria API. Unfortunately, doing so means that the signatures above need to be changed, as do some of the implementation code. The above model instead looks something like this:

```java
class FhirExampleDaoImpl extends BaseFhirDao<OpenmrsObject> implements FhirOpenmrsObjectDao {
    @Override
    protected <U> void setupSearchParams(OpenmrsFhirCriteriaContext<OpenmrsObject, U> criteriaContext, SearchParameterMap theParams) {
       // use the criteria object to build a JPA Criteria query that matches the `SearchParameterMap`
    }

   @Override
   protected <U> Path<?> paramToProp(@Nonnull OpenmrsFhirCriteriaContext<OpenmrsObject, U> criteriaContext, @Nonnull String param) {
      // take the name of the parameter (passed as the _sort param in the REST API)
      // return the corresponding `Path<?>` in the ORDER BY
   }

```

The big change is that instead of passing around a Hibernate `Criteria` object we’re now passing around a custom object called a `OpenmrsFhirCriteriaContext`. This is because emulating the behaviour of the Hibernate `Criteria` object requires passing around a few different objects in the JPA Criteria API.

Specifically, the `OpenmrsFhirCriteriaContext` gives you access to:

1. The current `EntityManager`
2. The `Root` object for the query, i.e., the class in the `from` clause
3. The `CriteriaQuery` object representing the current query being built
4. The `CriteriaBuilder` to build restrictions and expressions
5. A map of `Join` objects for any joins used in the query
6. A list of `Predicate` objects for any restrictions in the query
7. A list of `Order` objects for the `ORDER BY` part of the query

The idea is that by passing the `CriteriaContext` object around, we can build queries in a _similar_ way to how they were done with the Hibernate `Criteria` API.

Finally, a number of helpers have been refactored to be removed from the `BaseDao` and placed in a class called `FhirSearchQueryHelper`. The main goal here is just to try to keep `BaseDao` (and `BaseFhirDao`) a more manageable size as they were both a little difficult to understand.

Concretely, and using this to solve a marginally complex case, in FHIR2 2.x, we had this function.

```java
private void handleIngredientCode(Criteria criteria, TokenAndListParam ingredientCode) {
    if (ingredientCode != null) {
        criteria.createAlias("ingredients", "i");
        DetachedCriteria detachedCriteria = DetachedCriteria.forClass(Concept.class, "ic");
        handleCodeableConcept(criteria, ingredientCode, "ic", "icm", "icrt").ifPresent(detachedCriteria::add);
        detachedCriteria.setProjection(Projections.property("conceptId"));
        criteria.add(Subqueries.propertyIn("i.ingredient", detachedCriteria));
    }
}

```

This takes the `Criteria` representing the current query and a `TokenAndListParam` the represents the value(s) passed to the API via the `ingredient-code` search parameter. This would usually be a list of system-code pairs representing the concepts to search for.

This function aims to result in a query that’s something like this SQL:

```auto
SELECT * 
FROM drug d
INNER JOIN drug_ingredient i
    ON d.drug_id = i.drug_id
INNER JOIN concept ic
    ON i.ingredient_id = c.concept_id
WHERE (
    ic.concept_id in (
        SELECT c.concept_id
        FROM concept c
        WHERE c.uuid IN (?)
    )
)

```

To generate this same query in 3.x, we use this code:

```java
private <U> void handleIngredientCode(OpenmrsFhirCriteriaContext<Drug, U> criteriaContext,
        TokenAndListParam ingredientCode) {
    if (ingredientCode != null) {
        Join<?, ?> ingredientsJoin = criteriaContext.addJoin("ingredients", "i");
        Join<?, ?> conceptJoin = criteriaContext.addJoin(ingredientsJoin, "ingredient", "ic");
        
        OpenmrsFhirCriteriaSubquery<Concept, Integer> conceptSubquery = criteriaContext.addSubquery(Concept.class,
            Integer.class);
        getSearchQueryHelper().handleCodeableConcept(criteriaContext, ingredientCode, conceptJoin, "icm", "icrt")
                .ifPresent(conceptSubquery::addPredicate);
        conceptSubquery.setProjection(conceptSubquery.getRoot().get("conceptId"));
        
        criteriaContext.addPredicate(criteriaContext.getCriteriaBuilder().in(conceptJoin.get("conceptId"))
                .value(conceptSubquery.finalizeQuery()));
    }
}

```

Things here are a bit more explicit, as we had to call the `criteriaContext.addJoin()` (which is _sort of_ similar to `criteria.alias()`, though with some caveats noted in the Javadocs). We see the use of `getSearchQueryHelper()` to get the `FhirSearchQueryHelper` instance and that we can’t just specify properties by strings, but need to specify which objects they come from (mostly `context.getRoot().get("<property_name>")`.

Currently on the `master` branch of the FHIR2 module, we have all existing queries rewritten into this format, so there are lots of other examples to trawl through and as part of the 3.x upgrade, I’ve been trying to ensure we have substantially better documentation coverage with examples, etc.

Happy hacking!
