Elasticsearch¶
Real ES in a container or wired to an existing cluster. Save/get/delete by id, query via JSON DSL or the Java client builder, bulk ops via raw client().
Elasticsearch — wizard-synced snippet
Gradle
Stove configuration
Stove().with {
elasticsearch {
ElasticsearchSystemOptions(
configureExposedConfiguration = { cfg ->
listOf("elasticsearch.url=${cfg.url}")
}
)
}
}
Test DSL
In 30 seconds
Register elasticsearch { ElasticsearchSystemOptions(...) }. Use save(...), shouldGet<T>(index, key), shouldQuery<T>(jsonOrBuilder, index), shouldDelete(key, index). Drop into client() for bulk, aggregation, index admin.
Configure¶
Stove().with {
elasticsearch {
ElasticsearchSystemOptions(
container = ElasticContainerOptions(
disableSecurity = true, // simpler local testing
password = "changeme"
),
configureExposedConfiguration = { cfg ->
listOf(
"elasticsearch.url=${cfg.url}",
"elasticsearch.host=${cfg.host}",
"elasticsearch.port=${cfg.port}"
)
}
)
}
}.run()
| Field | Use |
|---|---|
container.disableSecurity |
Turn off TLS + auth for tests; true is sensible default |
container.password |
Used when security stays on |
clientConfigurer |
Customize the Java ElasticsearchClient (httpClient, SSL) |
jsonpMapper |
Override the JSON-P mapper (e.g. for Jakarta migration) |
serde |
Align with your app's mapper |
DSL¶
Save / fetch / delete¶
stove {
elasticsearch {
save(
id = "1",
instance = Product(id = "1", name = "Laptop", price = 999.99),
index = "products"
)
shouldGet<Product>(index = "products", key = "1") {
it.name shouldBe "Laptop"
}
shouldDelete(key = "1", index = "products")
}
}
Query¶
stove {
elasticsearch {
// JSON DSL
shouldQuery<Product>(
query = """
{ "query": { "range": { "price": { "gte": 100 } } } }
""".trimIndent(),
index = "products"
) { products ->
products.all { it.price >= 100 } shouldBe true
}
// Or via Java client builder
shouldQuery<Product>(
query = { q -> q.range { r -> r.field("price").gte(JsonData.of(100)) } },
index = "products"
) { /* ... */ }
}
}
Bulk / admin (raw client)¶
stove {
elasticsearch {
client().bulk { b ->
b.operations(/* ... */)
}
client().indices().create { it.index("orders") }
}
}
Migrations¶
class CreateProductsIndex : DatabaseMigration<ElasticsearchClient> {
override val order = 1
override suspend fun execute(client: ElasticsearchClient) {
client.indices().create { it.index("products") }
}
}
elasticsearch {
ElasticsearchSystemOptions(/* ... */).migrations {
register<CreateProductsIndex>()
}
}
Pitfalls¶
| Symptom | Fix |
|---|---|
unauthorized |
Set disableSecurity = true for local tests, or pass password |
Document not visible right after save |
ES is near-real-time; client().indices().refresh() or assert with timeout |
| Wrong index name | Mirror your app's index naming (prefix with run ID for shared infra) |
Pairs well with¶
- Provided Instances for shared ES (prefix index names)
- Recipes for multi-system flows