Skip to content

Nested Properties

Kotlin does not support chained :: property references. The DSL provides a / operator to compose property references into a type-safe path that works with all DSL operations.

The / operator

@Entity
class Organisation(
    @Id val id: Long,
    val name: String,
    @Embedded val addressInfo: AddressInfo,
)

@Embeddable
class AddressInfo(
    val street: String,
    val city: String,
)
val mainStreet = (Organisation::addressInfo / AddressInfo::street).equal("Main Street")
repository.findAll(mainStreet)

Multi-level nesting

The / operator is left-associative, so chains of any depth work naturally:

@Entity
class Organisation(
    @Id val id: Long,
    val name: String,
    @Embedded val organisationInfo: OrganisationInfo,
)

@Embeddable
class OrganisationInfo(
    @Embedded val addressInfo: AddressInfo,
)

@Embeddable
class AddressInfo(
    val street: String,
    val city: String,
)
val deepSpec = (Organisation::organisationInfo / OrganisationInfo::addressInfo / AddressInfo::street)
    .equal("Main Street")

Nullable associations

The / operator handles nullable associations transparently:

@Entity
class Persona(
    @Id val id: Long,
    val name: String,
    @OneToOne(fetch = FetchType.LAZY)
    val organisation: Organisation?,
)
// organisation is nullable, but the DSL handles it - no special syntax needed
val spec = (Persona::organisation / Organisation::name).equal("Acme Corp")

All operations work on nested properties

Every operation available on direct properties also works on nested properties:

// Equality
(Organisation::addressInfo / AddressInfo::street).equal("Main Street")
(Organisation::addressInfo / AddressInfo::street).notEqual("Main Street")

// Comparison
(Organisation::addressInfo / AddressInfo::street).greaterThan("M")
(Organisation::addressInfo / AddressInfo::street).between("A", "M")

// String
(Organisation::addressInfo / AddressInfo::street).like("Main%")
(Organisation::addressInfo / AddressInfo::street).notLike("Oak%")

// Nullability
(Organisation::addressInfo / AddressInfo::city).isNotNull()
(Organisation::addressInfo / AddressInfo::city).isNull()

// Inclusion
(Organisation::addressInfo / AddressInfo::street).`in`("Main Street")

// Combining nested specs
val spec = (Organisation::addressInfo / AddressInfo::street).like("Main%") and
        (Organisation::addressInfo / AddressInfo::city).isNotNull()

Nested property operations work with both Specification and PredicateSpecification.