Elasticsearch Client Integration
The criteria4s-elasticsearch-client module bridges criteria4s with the official Elasticsearch Java client. It provides extension methods and implicit conversions that turn a Criteria[Elasticsearch] into an Elasticsearch Query object via the WrapperQuery mechanism, so you can use your type-safe criteria directly in search requests.
Dependency
libraryDependencies += "com.eff3ct" %% "criteria4s-elasticsearch-client" % "1.0.0"
This module transitively depends on criteria4s-elasticsearch and the Elasticsearch Java client (co.elastic.clients:elasticsearch-java).
Import Pattern
import com.eff3ct.criteria4s.core.*
import com.eff3ct.criteria4s.dialect.elasticsearch.{*, given}
import com.eff3ct.criteria4s.dialect.elasticsearch.client.given
import com.eff3ct.criteria4s.functions as F
import com.eff3ct.criteria4s.extensions.*
The key import is com.eff3ct.criteria4s.dialect.elasticsearch.client.given, which brings in .toQuery and the implicit Conversion[Criteria[Elasticsearch], Query].
API
.toQuery
Converts a criteria to an Elasticsearch Query:
val filter: Criteria[Elasticsearch] = F.col[Elasticsearch]("age").geq(F.lit[Elasticsearch, Int](18))
val query: Query = filter.toQuery
Implicit Conversion to Query
The client package provides a given Conversion[Criteria[Elasticsearch], Query], so you can pass criteria directly to the search request builder:
import com.eff3ct.criteria4s.dialect.elasticsearch.client.given
val filter: Criteria[Elasticsearch] = F.col[Elasticsearch]("status")
.===(F.lit[Elasticsearch, String]("active"))
// Use in search request builder -- implicit conversion to Query
client.search(s => s.index("users").query(filter), classOf[User])
How It Works
The conversion uses Elasticsearch's WrapperQuery, which accepts a base64-encoded JSON query string. When you call .toQuery or trigger the implicit conversion, the steps are:
- Take the
Criteria.valuestring (a JSON Query DSL expression) - Encode it as UTF-8 bytes
- Base64-encode those bytes
- Wrap them in a
WrapperQuery - Convert the
WrapperQueryto aQuery
This means the full Query DSL JSON produced by the Elasticsearch dialect is sent to Elasticsearch as-is, preserving the exact query structure.
Example with SearchRequest
The following example shows how to use criteria4s with the Elasticsearch Java client. This is a plain code example since the ES client is not available in the docs classpath.
import co.elastic.clients.elasticsearch.ElasticsearchClient
import co.elastic.clients.elasticsearch._types.query_dsl.Query
import co.elastic.clients.elasticsearch.core.SearchResponse
import com.eff3ct.criteria4s.core.*
import com.eff3ct.criteria4s.dialect.elasticsearch.{*, given}
import com.eff3ct.criteria4s.dialect.elasticsearch.client.given
import com.eff3ct.criteria4s.functions as F
import com.eff3ct.criteria4s.extensions.*
// Assume `client` is an initialized ElasticsearchClient
// Build a type-safe filter
val filter = F.col[Elasticsearch]("age")
.geq(F.lit[Elasticsearch, Int](21))
.and(F.col[Elasticsearch]("status") === F.lit[Elasticsearch, String]("active"))
// Use with search -- implicit conversion to Query
val response: SearchResponse[java.util.Map[_, _]] = client.search(
s => s.index("users").query(filter),
classOf[java.util.Map[_, _]]
)
response.hits().hits().forEach { hit =>
println(s"Score: ${hit.score()}, Source: ${hit.source()}")
}
// Or use .toQuery explicitly
val query: Query = filter.toQuery
val response2 = client.search(
s => s.index("users").query(query),
classOf[java.util.Map[_, _]]
)
Combining Queries
You can compose criteria4s filters with other Elasticsearch queries by converting to Query first and then passing it into a BoolQuery builder:
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery
val criteria4sFilter: Query = filter.toQuery
val combinedQuery = BoolQuery.of { b =>
b.must(criteria4sFilter)
.filter(f => f.range(r => r.field("created_at").gte(co.elastic.clients.json.JsonData.of("2025-01-01"))))
}
client.search(
s => s.index("users").query(q => q.bool(combinedQuery)),
classOf[java.util.Map[_, _]]
)