Understanding Metamodels
Metalastic automatically generates type-safe metamodel classes from your Spring Data Elasticsearch @Document annotated classes. These metamodels provide compile-time safety and IDE auto-completion for building Elasticsearch queries.
What are Metamodels?
A metamodel is a generated Kotlin class that mirrors your Elasticsearch document structure. It provides type-safe accessors for all fields, enabling you to reference document fields without using string literals.
Example
Given this Spring Data Elasticsearch document:
@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;
}Metalastic generates:
class MetaProduct<T : Any?>(
parent: ObjectField<*>? = null,
name: String = "",
nested: Boolean = false,
fieldType: KType,
) : Document<T>(parent = parent, name = name, nested = nested, fieldType = fieldType) {
@JvmField
val id: KeywordField<String> = keyword("id")
@JvmField
val title: TextField<String> = text("title")
@JvmField
val price: DoubleField<Double> = double("price")
override fun indexName(): String = INDEX_NAME
companion object {
const val INDEX_NAME: String = "products"
@JvmField
val product: MetaProduct<Product> = MetaProduct(fieldType = typeOf<Product>())
}
}Key details:
fieldTypeparameter has no default value - must be provided explicitly- Companion object includes
INDEX_NAMEconstant for the index name - Singleton instance
productis created with explicitfieldType = typeOf<Product>() - When used as a nested field in another document (e.g.,
val products: List<Product>), the parent provides the fieldType:MetaProduct(this, "products", false, typeOf<List<Product>>())
How Generation Works
Metalastic uses KSP (Kotlin Symbol Processing) to generate metamodels during compilation. The processor runs in three phases:
1. COLLECTING Phase
- Discovers all
@Documentannotated classes - Builds a dependency graph for nested types
- Resolves circular references
- Collects field metadata from
@Fieldannotations
2. BUILDING Phase
- Generates KotlinPoet
TypeSpecobjects for each metamodel - Builds the centralized
Metamodelsregistry - Applies type transformations
- Optimizes imports and resolves conflicts
3. WRITING Phase
- Writes generated files to
build/generated/ksp/{sourceSet}/kotlin/ - Generates optional debug reports
- Tracks performance metrics
The Type System
Generic Type Parameters
All metamodel classes use generic type parameters with runtime type tracking:
class MetaProduct<T : Any?>(
parent: ObjectField<*>? = null,
name: String = "",
nested: Boolean = false,
fieldType: KType = typeOf<Product>(),
) : Document<T>(parent, name, nested, fieldType)The T : Any? parameter allows the metamodel to be reused in different contexts while maintaining type safety.
Runtime Type Tracking
Every field includes runtime type information via Kotlin's KType:
@JvmField
val title: TextField<String> = text("title") // fieldType = typeOf<String>()This enables:
- Advanced type-safe query building
- Runtime type validation
- Custom type converters
- Generic query construction
Field Type Hierarchy
All field types extend from a common base:
Metamodel<T>
├── Container<T>
├── ObjectField<T>
│ ├── Document<T> (your @Document classes)
│ └── Inner classes (nested objects)
└── Field<T, Self>
├── TextField<T>
├── KeywordField<T>
├── IntegerField<T>
├── LongField<T>
├── DoubleField<T>
├── DateField<T>
└── ...Path Traversal
Metamodels automatically build Elasticsearch field paths through parent hierarchy traversal:
import com.example.MetaProduct.Companion.product
// Simple field
product.title.path() // "title"
// Nested object
product.category.name.path() // "category.name"
// Nested field (with nested = true)
product.tags.value.path() // "tags.value"
product.tags.value.isNestedPath() // true
product.tags.value.nestedPaths().toList() // ["tags"]Metamodels Registry
Metalastic generates a centralized registry for all metamodels:
object Metamodels {
@JvmStatic
fun entries(): Sequence<Document<*>> = sequenceOf(
product,
category,
// ... all generated metamodels
)
}This allows you to:
- Iterate over all metamodels
- Build generic utilities
- Implement dynamic query builders
- Create testing helpers
Java Compatibility
All generated metamodels include @JvmField annotations for seamless Java interop:
// Java usage
import static com.example.MetaProduct.product;
String titlePath = product.title.path(); // "title"Next Steps
- Field Types Reference - Learn about all supported field types
- Query DSL Guide - Build type-safe queries with generated metamodels
- Configuration - Customize metamodel generation
