Automatic Code Generation
KSP annotation processor transforms your @Document classes into type-safe Meta-classes at compile time. Zero runtime overhead, instant IDE support.
Automatically generate compile-time validated field accessors from your Spring Data Elasticsearch documents

Elasticsearch queries using Spring Data rely on string-based field names:
// String-based field names are error-prone
NativeSearchQuery query = new NativeSearchQueryBuilder()
.withQuery(
QueryBuilders.boolQuery()
.must(QueryBuilders.termQuery("staus", "active")) // ❌ Typo: "staus" → "status"
.filter(QueryBuilders.rangeQuery("prcie").gte(100)) // ❌ Typo: "prcie" → "price"
)
.build();
// No IDE autocomplete, no refactoring support, no compile-time validation
// Errors only discovered at runtime!Metalastic generates type-safe metamodels from your @Document classes:
@Document(indexName = "products")
public class Product {
@Field(type = FieldType.Keyword)
private String id;
@Field(type = FieldType.Text)
private String title;
@Field(type = FieldType.Double)
private Double price;
@Field(type = FieldType.Keyword)
private ProductStatus status;
@Field(type = FieldType.Object)
private Category category;
}// Generated MetaProduct.kt - no manual work required!
class MetaProduct<T>(/* ... */) : Document<T>(/* ... */) {
val id: KeywordField<String> = keyword("id")
val title: TextField<String> = text("title")
val price: DoubleField<Double> = double("price")
val status: KeywordField<ProductStatus> = keyword("status")
val category: MetaCategory<Category> = MetaCategory(this, "category", false, typeOf<Category>())
companion object {
const val INDEX_NAME = "products"
val product: MetaProduct<Product> = MetaProduct()
}
}import com.example.MetaProduct.Companion.product
// ✅ Compile-time validated field access
product.title.path() // "title"
product.category.name.path() // "category.name" (automatic dotted notation)
// ✅ No typos possible - won't compile if field doesn't exist!
val query = QueryBuilders.boolQuery()
.must(QueryBuilders.termQuery(product.status.path(), ProductStatus.ACTIVE))
.filter(QueryBuilders.rangeQuery(product.price.path()).gte(100))
// IDE autocomplete shows all available fields ✨For maximum type safety, add the Query DSL module:
import com.metalastic.dsl.*
import com.example.MetaProduct.Companion.product
// Fluent, type-safe query building
val query = BoolQuery.of {
boolQueryDsl {
must + {
product.title match "laptop"
product.status term ProductStatus.ACTIVE
}
filter + {
product.price range Range.closed(500.0, 2000.0)
product.category.name term "electronics"
}
}
}Metalastic uses a KSP annotation processor that runs at compile time:
@Document annotated classesFiles are generated to build/generated/ksp/ and appear automatically in your IDE.
Learn More
See Understanding Metamodels for the complete architecture, three-phase processing details, and type system explanation.
plugins {
id("com.google.devtools.ksp") version "2.3.3"
id("com.ekino.oss.metalastic") version "1.1.0"
}
metalastic {
metamodels {
packageName = "com.example.search"
className = "Metamodels"
}
}plugins {
id("com.google.devtools.ksp") version "2.3.3"
}
dependencies {
implementation("com.ekino.oss:metalastic-core:1.1.0")
ksp("com.ekino.oss:metalastic-processor:1.1.0")
}
ksp {
arg("metamodels.package", "com.example.search")
arg("metamodels.className", "Metamodels")
}Use generated metamodels with any query builder:
dependencies {
implementation("com.ekino.oss:metalastic-elasticsearch-dsl:1.1.0")
}Adds fluent query API with:
must + { })For Developers:
For Teams:
For Your Application:
Ready to eliminate string-based field names?
Follow our Quick Start guide to transform your Elasticsearch queries in 5 minutes.