Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions docs/tutorials/kotlin.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,62 @@ You also need the Querydsl module for your backend (e.g. `querydsl-jpa`,
`querydsl-sql`). Code generation works the same as with Java — use the
annotation processor for JPA or the Maven plugin for SQL.

## Code Generation for Kotlin

There are two ways to generate Q-classes for a Kotlin codebase. Pick based on
your build system and how your entities are defined.

### Pure Kotlin entities (Gradle, KSP)

If your entities are Kotlin classes, use `querydsl-ksp-codegen` via the KSP
Gradle plugin. KSP runs as part of `kspKotlin` and emits `.kt` Q-classes:

```kotlin
plugins {
kotlin("jvm")
id("com.google.devtools.ksp") version "<ksp-version>"
kotlin("plugin.jpa")
}

dependencies {
implementation("{{ site.group_id }}:querydsl-jpa:{{ site.querydsl_version }}")
ksp("{{ site.group_id }}:querydsl-ksp-codegen:{{ site.querydsl_version }}")
}
```

See the [`querydsl-ksp-codegen` README](https://github.com/OpenFeign/querydsl/blob/master/querydsl-tooling/querydsl-ksp-codegen/readme.md)
for the full list of `querydsl.*` settings (prefix, suffix, package suffix,
include/exclude filters).

### Mixed Java/Kotlin entities (Gradle, KSP)

When your project mixes Java entities with Kotlin queries, the standard
`querydsl-apt` Java processor doesn't work cleanly: Gradle compiles Kotlin
before Java, so Kotlin code can't see the Java Q-classes generated by APT.
`querydsl-ksp-codegen` handles this case — it picks up Java `@Entity` /
`@Embeddable` / `@MappedSuperclass` classes during `kspKotlin` and emits
Kotlin Q-classes that compile alongside your Kotlin sources.

The Gradle setup is the same as for pure-Kotlin entities — no extra
configuration is needed beyond keeping your Java entities under `src/main/java`.
The runnable example
[`querydsl-examples/querydsl-example-ksp-codegen`](https://github.com/OpenFeign/querydsl/tree/master/querydsl-examples/querydsl-example-ksp-codegen)
mixes Kotlin entities (`Person`, `Cat`, …) with a Java entity (`Branch`,
self-referencing), all queried side-by-side from Kotlin tests.

### Pure Kotlin entities (Maven, KAPT)

KSP is not natively supported by `kotlin-maven-plugin`. On Maven, use KAPT
with `querydsl-kotlin-codegen` (which produces the same Kotlin Q-classes via
the legacy APT pipeline). See
[`querydsl-examples/querydsl-example-kotlin-codegen`](https://github.com/OpenFeign/querydsl/tree/master/querydsl-examples/querydsl-example-kotlin-codegen)
for a Maven-based reference.

### Java entities only

If your entities and queries are both Java, stick with `querydsl-apt` —
nothing on this page applies.

## Kotlin Operator Extensions

The `querydsl-kotlin` module provides operator overloads for Querydsl
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Expand Down
15 changes: 7 additions & 8 deletions querydsl-examples/querydsl-example-ksp-codegen/gradlew

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

54 changes: 22 additions & 32 deletions querydsl-examples/querydsl-example-ksp-codegen/gradlew.bat

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.querydsl.example.ksp;

import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;

/**
* A Java entity living alongside the Kotlin entities in this example.
*
* <p>Demonstrates the typical reason to reach for {@code querydsl-ksp-codegen}:
* with {@code querydsl-apt} a mixed Java/Kotlin Gradle project hits a chicken-and-egg
* compile order — Kotlin compiles before Java, so the Java Q-classes APT generates
* aren't on the classpath when Kotlin sources reference them. KSP runs as part of
* {@code kspKotlin}, sees Java sources alongside Kotlin sources, and emits {@code .kt}
* Q-classes that compile cleanly with the rest of the Kotlin code.
*
* <p>Also exercises the self-reference case: {@code parent} is itself a {@code Branch},
* which under eager initialisation would stack-overflow at construction. The KSP
* processor emits the field as {@code by lazy} so each access creates one more level
* on demand.
*/
@Entity
public class Branch {

@Id private Long id;

private String name;

@ManyToOne private Branch parent;

public Branch() {}

public Branch(Long id, String name, Branch parent) {
this.id = id;
this.name = name;
this.parent = parent;
}

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Branch getParent() {
return parent;
}

public void setParent(Branch parent) {
this.parent = parent;
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import com.querydsl.example.ksp.Bear
import com.querydsl.example.ksp.BearSpecies
import com.querydsl.example.ksp.Branch
import com.querydsl.example.ksp.Cat
import com.querydsl.example.ksp.CatType
import com.querydsl.example.ksp.Dog
import com.querydsl.example.ksp.Email
import com.querydsl.example.ksp.Person
import com.querydsl.example.ksp.QBear
import com.querydsl.example.ksp.QBearSimplifiedProjection
import com.querydsl.example.ksp.QBranch
import com.querydsl.example.ksp.QCat
import com.querydsl.example.ksp.QDog
import com.querydsl.example.ksp.QMyShape
Expand Down Expand Up @@ -283,6 +285,36 @@ class Tests {
}
}

@Test
fun `select java entity from kotlin code`() {
// Branch is a Java @Entity sitting alongside the Kotlin entities. Demonstrates
// that querydsl-ksp-codegen picks up Java sources during kspKotlin and emits
// a .kt Q-class (QBranch) that Kotlin can use the same as QPerson, QCat, etc.
// Also exercises the self-reference shape: Branch.parent : Branch is rendered
// as `by lazy` non-null QBranch, so navigating arbitrary chains never overflows.
val emf = initialize()
val em = emf.createEntityManager()
em.transaction.begin()
val root = Branch(1L, "root", null)
em.persist(root)
em.persist(Branch(2L, "child", root))
em.transaction.commit()

val q = QBranch.branch
val child = JPAQueryFactory(em)
.selectFrom(q)
.where(q.name.eq("child"))
.fetchOne()

if (child == null) {
fail<Any>("No child Branch was returned")
} else {
assertThat(child.id).isEqualTo(2L)
assertThat(child.parent.id).isEqualTo(1L)
}
em.close()
}

@Test
fun ensureCorrectGeoType() {
val departureProperty = QMyShape::class.memberProperties.single { it.name == "departureGeo" }
Expand All @@ -300,6 +332,7 @@ class Tests {
.addAnnotatedClass(Cat::class.java)
.addAnnotatedClass(Dog::class.java)
.addAnnotatedClass(Bear::class.java)
.addAnnotatedClass(Branch::class.java)

return configuration
.buildSessionFactory()
Expand Down
39 changes: 39 additions & 0 deletions querydsl-tooling/querydsl-ksp-codegen/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,45 @@
<artifactId>kotlin-script-runtime</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>dev.zacsweers.kctfork</groupId>
<artifactId>core</artifactId>
<version>0.12.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>dev.zacsweers.kctfork</groupId>
<artifactId>ksp</artifactId>
<version>0.12.1</version>
<scope>test</scope>
<exclusions>
<!-- com.google.devtools.ksp:symbol-processing is a pom-packaged aggregator;
kctfork's gradle-published metadata declares it without type=pom, so
Maven mistakenly tries to download a non-existent jar. The runtime
artifacts kctfork actually needs (symbol-processing-aa-embeddable,
symbol-processing-common-deps, symbol-processing-api) are declared
separately in kctfork's pom and resolve fine. -->
<exclusion>
<groupId>com.google.devtools.ksp</groupId>
<artifactId>symbol-processing</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Align KSP runtime artifacts with the API version pinned in the root pom.
kctfork 0.12.1 pulls in 2.3.4; the API is at ${ksp.version}=2.3.7. The
mismatch can surface as KaInvalidLifetimeOwnerAccessException at runtime. -->
<dependency>
<groupId>com.google.devtools.ksp</groupId>
<artifactId>symbol-processing-aa-embeddable</artifactId>
<version>${ksp.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.devtools.ksp</groupId>
<artifactId>symbol-processing-common-deps</artifactId>
<version>${ksp.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Loading
Loading