
Integrate Algolia within projects, offering seamless JSON parsing, asynchronous operations, and thread-safe clients. Features include coroutine support, type safety, and comprehensive documentation links embedded in source files.
Documentation • Community Forum • Stack Overflow • Report a bug • Support
1.6 and higher.SearchClient, AnalyticsClient, and InsightsClient in a multithreaded environment.Install the Kotlin client by adding the following dependency to your gradle.build file:
repositories {
mavenCentral()
}
dependencies {
implementation "com.algolia:algoliasearch-client-kotlin:$version"
}Also, choose and add to your dependencies one of Ktor http client engines. Alternatively, you can use algoliasearch-client-kotlin-bom.
For full documentation, visit the Algolia Kotlin API Client.
The client includes an optional Kotlin DSL to build search parameters, index settings, rules, and filters. The DSL is experimental: opt in with @OptIn(AlgoliaExperimentalDsl::class). It builds the same models as the data-class constructors, which remain fully supported.
[!WARNING] The DSL isn't source compatible with version 2. Many names are the same, but some behaviors changed. See Migrating from version 2.
import com.algolia.client.dsl.*
@OptIn(AlgoliaExperimentalDsl::class)
val response = client.searchSingleIndex("products") {
query = "shoes"
hitsPerPage = 20
filters {
orFacet { facet("brand", "Acme"); facet("brand", "Globex") }
facet("category", "outlet", isNegated = true)
}
optionalFilters {
or { facet("color", "red", score = 2); facet("color", "blue") }
}
}filters { } builds the filters string. Top-level filters are joined with AND. Use orFacet { }, orTag { }, or orNumeric { } for OR groups.optionalFilters { } builds the optionalFilters field, with and { } and or { } groups of facets, each with an optional score.isNegated = true negates a single filter. Pass it by name: the third positional argument of facet is score.query { } builds a SearchParamsObject without sending it. browse { } does the same for client.browse:
@OptIn(AlgoliaExperimentalDsl::class)
client.browse("products", browse { query = "shoes"; filters { facet("brand", "Acme") } })List parameters take + entries. A block replaces the list, and an empty block sends []:
import com.algolia.client.dsl.*
import com.algolia.client.model.search.SupportedLanguage
@OptIn(AlgoliaExperimentalDsl::class)
val params = query {
restrictSearchableAttributes { +"title"; +"description" }
queryLanguages { +SupportedLanguage.En }
}client.deleteBy(indexName) { filters { … } } deletes the matching records. Unlike search, the delete DSL never widens its scope: it throws IllegalArgumentException before sending the request if a filter block or group is empty, or if there's no filter or geo condition at all.
import com.algolia.client.dsl.*
import com.algolia.client.dsl.rule.*
@OptIn(AlgoliaExperimentalDsl::class)
val indexSettings = settings {
searchableAttributes {
ordered("name")
unordered("description")
}
}
@OptIn(AlgoliaExperimentalDsl::class)
val promo = rule("promo") {
consequence {
promote { objectID("featured-1", position = 0) }
hide { +"discontinued-1" }
}
}A rule requires consequence.
Store fragments as lambdas on the DSL receivers and apply them anywhere:
import com.algolia.client.dsl.*
import com.algolia.client.dsl.filter.*
@OptIn(AlgoliaExperimentalDsl::class)
val locale: DSLFilters.() -> Unit = { orFacet { facet("locale", "en-US") } }
@OptIn(AlgoliaExperimentalDsl::class)
val base: DSLQuery.() -> Unit = { hitsPerPage = 20; filters(locale) }The receivers are DSLQuery, DSLBrowse, DSLDeleteBy, DSLSettings, DSLQueryAdditions, DSLDeleteByAdditions, DSLFilters, and DSLFacetFilters. Some are typealiases of generated classes, so Java code shows the generated names, such as DSLSearchParamsObject.
DSLQueryComposer builds one SearchParamsObject from fragments contributed by different parts of your code:
@OptIn(AlgoliaExperimentalDsl::class)
val composer = DSLQueryComposer(base = { hitsPerPage = 20 })
composer.add { filters { facet("brand", "Acme") } } // merged with other fragments
composer.add { ruleContexts { +"mobile" } }
composer.override { sumOrFiltersScores = true } // applied last
client.searchSingleIndex(indexName, composer.build())add { } fragments are merged per field: filters fragments are joined with AND, and list fragments are appended.override { } blocks run after the merge, in call order. The last write wins.build(), not when you add them, so captured variables are read at build time.DSLQueryComposer(from = existingParams) starts from an existing object without modifying it. Fields with add { } fragments replace the object's value instead of merging with it.DSLDeleteByComposer works the same way for deleteBy.| Version 2 | Version 3 |
|---|---|
index.search(query) |
client.searchSingleIndex(indexName) { } |
index.browse(query) |
client.browse(indexName, browse { }) |
index.deleteObjectsBy(DeleteByQuery().apply { … }) |
client.deleteBy(indexName) { filters { } } |
initIndex |
removed: pass the index name to each method |
Query(...), query { }
|
query { } → SearchParamsObject
|
Settings |
settings { } → IndexSettings
|
Attribute("x"), UserToken("u")
|
"x", "u"
|
facetFilters { }, numericFilters { }, tagFilters { }
|
filters { } |
unary ! on a Filter
|
isNegated = true on each filter |
Language.English |
SupportedLanguage.En |
Distinct(1), TypoTolerance.Min
|
Distinct.of(1), TypoTolerance.of(TypoToleranceEnum.Min)
|
IgnorePlurals.True, RemoveStopWords.True
|
IgnorePlurals.of(true), RemoveStopWords.of(true)
|
ResponseFields.Hits |
+"hits" |
DSLFilters, DSLFacetFilters, the filter and group types, and the list and settings receivers keep their version 2 names. DSLConditions, DSLPromotions, and DSLObjectIDs keep their names but have different members.
These compile but can change what's sent to the engine:
filters and didn't escape \. Version 3 quotes only when needed and escapes \ and ". Update tests that compare generated filter strings.and { } no longer adds parentheses: it's flattened into the top-level AND. Groups keep their call order, and duplicates are no longer removed.optionalFilters doesn't use the filters syntax: each entry is matched literally as attribute:value. Version 3 sends quotes in values as-is (version 2 escaped them as \") and escapes a leading - as \-, so the engine doesn't read it as a negation. The version 2 escape parameter is gone.deleteBy rather than deleting more records than intended.SearchClient.search is multi-query. Use searchSingleIndex for a single index.See the Kotlin upgrade guide.
Encountering an issue? Before reaching out to support, we recommend heading to our FAQ where you will find answers for the most common issues and gotchas with the client.
If you want to contribute to this project without installing all its dependencies, you can use our Docker image. Please check our dedicated guide to learn more.
The Algolia Kotlin API Client is an open-sourced software licensed under the MIT license.
Documentation • Community Forum • Stack Overflow • Report a bug • Support
1.6 and higher.SearchClient, AnalyticsClient, and InsightsClient in a multithreaded environment.Install the Kotlin client by adding the following dependency to your gradle.build file:
repositories {
mavenCentral()
}
dependencies {
implementation "com.algolia:algoliasearch-client-kotlin:$version"
}Also, choose and add to your dependencies one of Ktor http client engines. Alternatively, you can use algoliasearch-client-kotlin-bom.
For full documentation, visit the Algolia Kotlin API Client.
The client includes an optional Kotlin DSL to build search parameters, index settings, rules, and filters. The DSL is experimental: opt in with @OptIn(AlgoliaExperimentalDsl::class). It builds the same models as the data-class constructors, which remain fully supported.
[!WARNING] The DSL isn't source compatible with version 2. Many names are the same, but some behaviors changed. See Migrating from version 2.
import com.algolia.client.dsl.*
@OptIn(AlgoliaExperimentalDsl::class)
val response = client.searchSingleIndex("products") {
query = "shoes"
hitsPerPage = 20
filters {
orFacet { facet("brand", "Acme"); facet("brand", "Globex") }
facet("category", "outlet", isNegated = true)
}
optionalFilters {
or { facet("color", "red", score = 2); facet("color", "blue") }
}
}filters { } builds the filters string. Top-level filters are joined with AND. Use orFacet { }, orTag { }, or orNumeric { } for OR groups.optionalFilters { } builds the optionalFilters field, with and { } and or { } groups of facets, each with an optional score.isNegated = true negates a single filter. Pass it by name: the third positional argument of facet is score.query { } builds a SearchParamsObject without sending it. browse { } does the same for client.browse:
@OptIn(AlgoliaExperimentalDsl::class)
client.browse("products", browse { query = "shoes"; filters { facet("brand", "Acme") } })List parameters take + entries. A block replaces the list, and an empty block sends []:
import com.algolia.client.dsl.*
import com.algolia.client.model.search.SupportedLanguage
@OptIn(AlgoliaExperimentalDsl::class)
val params = query {
restrictSearchableAttributes { +"title"; +"description" }
queryLanguages { +SupportedLanguage.En }
}client.deleteBy(indexName) { filters { … } } deletes the matching records. Unlike search, the delete DSL never widens its scope: it throws IllegalArgumentException before sending the request if a filter block or group is empty, or if there's no filter or geo condition at all.
import com.algolia.client.dsl.*
import com.algolia.client.dsl.rule.*
@OptIn(AlgoliaExperimentalDsl::class)
val indexSettings = settings {
searchableAttributes {
ordered("name")
unordered("description")
}
}
@OptIn(AlgoliaExperimentalDsl::class)
val promo = rule("promo") {
consequence {
promote { objectID("featured-1", position = 0) }
hide { +"discontinued-1" }
}
}A rule requires consequence.
Store fragments as lambdas on the DSL receivers and apply them anywhere:
import com.algolia.client.dsl.*
import com.algolia.client.dsl.filter.*
@OptIn(AlgoliaExperimentalDsl::class)
val locale: DSLFilters.() -> Unit = { orFacet { facet("locale", "en-US") } }
@OptIn(AlgoliaExperimentalDsl::class)
val base: DSLQuery.() -> Unit = { hitsPerPage = 20; filters(locale) }The receivers are DSLQuery, DSLBrowse, DSLDeleteBy, DSLSettings, DSLQueryAdditions, DSLDeleteByAdditions, DSLFilters, and DSLFacetFilters. Some are typealiases of generated classes, so Java code shows the generated names, such as DSLSearchParamsObject.
DSLQueryComposer builds one SearchParamsObject from fragments contributed by different parts of your code:
@OptIn(AlgoliaExperimentalDsl::class)
val composer = DSLQueryComposer(base = { hitsPerPage = 20 })
composer.add { filters { facet("brand", "Acme") } } // merged with other fragments
composer.add { ruleContexts { +"mobile" } }
composer.override { sumOrFiltersScores = true } // applied last
client.searchSingleIndex(indexName, composer.build())add { } fragments are merged per field: filters fragments are joined with AND, and list fragments are appended.override { } blocks run after the merge, in call order. The last write wins.build(), not when you add them, so captured variables are read at build time.DSLQueryComposer(from = existingParams) starts from an existing object without modifying it. Fields with add { } fragments replace the object's value instead of merging with it.DSLDeleteByComposer works the same way for deleteBy.| Version 2 | Version 3 |
|---|---|
index.search(query) |
client.searchSingleIndex(indexName) { } |
index.browse(query) |
client.browse(indexName, browse { }) |
index.deleteObjectsBy(DeleteByQuery().apply { … }) |
client.deleteBy(indexName) { filters { } } |
initIndex |
removed: pass the index name to each method |
Query(...), query { }
|
query { } → SearchParamsObject
|
Settings |
settings { } → IndexSettings
|
Attribute("x"), UserToken("u")
|
"x", "u"
|
facetFilters { }, numericFilters { }, tagFilters { }
|
filters { } |
unary ! on a Filter
|
isNegated = true on each filter |
Language.English |
SupportedLanguage.En |
Distinct(1), TypoTolerance.Min
|
Distinct.of(1), TypoTolerance.of(TypoToleranceEnum.Min)
|
IgnorePlurals.True, RemoveStopWords.True
|
IgnorePlurals.of(true), RemoveStopWords.of(true)
|
ResponseFields.Hits |
+"hits" |
DSLFilters, DSLFacetFilters, the filter and group types, and the list and settings receivers keep their version 2 names. DSLConditions, DSLPromotions, and DSLObjectIDs keep their names but have different members.
These compile but can change what's sent to the engine:
filters and didn't escape \. Version 3 quotes only when needed and escapes \ and ". Update tests that compare generated filter strings.and { } no longer adds parentheses: it's flattened into the top-level AND. Groups keep their call order, and duplicates are no longer removed.optionalFilters doesn't use the filters syntax: each entry is matched literally as attribute:value. Version 3 sends quotes in values as-is (version 2 escaped them as \") and escapes a leading - as \-, so the engine doesn't read it as a negation. The version 2 escape parameter is gone.deleteBy rather than deleting more records than intended.SearchClient.search is multi-query. Use searchSingleIndex for a single index.See the Kotlin upgrade guide.
Encountering an issue? Before reaching out to support, we recommend heading to our FAQ where you will find answers for the most common issues and gotchas with the client.
If you want to contribute to this project without installing all its dependencies, you can use our Docker image. Please check our dedicated guide to learn more.
The Algolia Kotlin API Client is an open-sourced software licensed under the MIT license.