Skip to content

Commit 0b6378f

Browse files
JordanJLopezPaul BerkmanJordan Lopez
authored
BREAKING CHANGE: Upgrade Kotlin to v2.3.0, graphql-java to v24.3 (#2147)
### 📝 Description Upgrade graphql-java from 23.1 to 24.3 Incorporates #2140 as well https://github.com/graphql-java/graphql-java/releases/tag/v24.0 Dependency | Before | After | Release Notes | |---|---|---|---| | graphql\-java | 23.1 | 24.3 | [graphql\-java releases](https://github.com/graphql-java/graphql-java/releases) | | kotlin | 2.0.0 | 2.3.0 | [Kotlin releases](https://github.com/JetBrains/kotlin/releases) | | kotlinx\-coroutines | 1.9.0 | 1.10.2 | [kotlinx.coroutines releases](https://github.com/Kotlin/kotlinx.coroutines/releases) | | kotlinx\-serialization | 1.6.3 | 1.10.0 | [kotlinx.serialization releases](https://github.com/Kotlin/kotlinx.serialization/releases) | | ktor | 3.2.3 | 3.3.0 | [Ktor releases](https://github.com/ktorio/ktor/releases) | | compile\-testing | 0.5.1 | 0.10.0 | [kctfork releases](https://github.com/ZacSweers/kotlin-compile-testing/releases) | ### 🔗 Related Issues --------- Co-authored-by: Paul Berkman <paul.berkman@microba.com> Co-authored-by: Jordan Lopez <jordlopez@expediagroup.com>
1 parent 2e6e336 commit 0b6378f

File tree

21 files changed

+158
-68
lines changed

21 files changed

+158
-68
lines changed
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Migration Guide: Upgrading to graphql-java v24.x and java-dataloader v4.x/v5.x
2+
3+
This guide covers breaking changes and required code updates when upgrading to graphql-java v24.x and java-dataloader v4.x/v5.x, as well as the impact on downstream users of this library.
4+
5+
## Key Breaking Changes
6+
7+
### 1. DataLoaderOptions is now immutable
8+
- The mutative `setXXX` methods (e.g., `setStatisticsCollector`) now return a builder, not a DataLoaderOptions instance.
9+
- You must call `.build()` after setting options to get a DataLoaderOptions instance.
10+
- Example:
11+
```kotlin
12+
val options = DataLoaderOptions.newOptions()
13+
.setStatisticsCollector(::SimpleStatisticsCollector)
14+
.build()
15+
```
16+
17+
### 2. BatchLoader lambda typing
18+
- The lambda passed to `DataLoaderFactory.newDataLoader` must have its parameter types specified explicitly.
19+
- Example:
20+
```kotlin
21+
DataLoaderFactory.newDataLoader(
22+
{ keys: List<MyKeyType> -> ... },
23+
options
24+
)
25+
```
26+
27+
### 3. Removal of mutative methods
28+
- Any code using the old mutative methods (e.g., `.setStatisticsCollector(...)` without `.build()`) will break.
29+
30+
### 4. DataLoaderOptions builder pattern
31+
- All customizations must use the builder pattern and call `.build()`.
32+
33+
## Example Migration
34+
35+
**Before:**
36+
```kotlin
37+
DataLoaderFactory.newDataLoader(
38+
{ keys -> ... },
39+
DataLoaderOptions.newOptions().setStatisticsCollector(::SimpleStatisticsCollector)
40+
)
41+
```
42+
43+
**After:**
44+
```kotlin
45+
DataLoaderFactory.newDataLoader(
46+
{ keys: List<MyKeyType> -> ... },
47+
DataLoaderOptions.newOptions()
48+
.setStatisticsCollector(::SimpleStatisticsCollector)
49+
.build()
50+
)
51+
```
52+
53+
## Impact on Downstream Users
54+
- If you expose APIs or extension points that expect users to provide or customize DataLoader/DataLoaderOptions, or if you document/test usage patterns that are now broken, your downstream users will need to update their code to match the new APIs.
55+
- Any direct usage of DataLoaderOptions or DataLoaderFactory will require migration as shown above.
56+
57+
## Recommendations
58+
- Update your own documentation and migration guides to highlight these breaking changes.
59+
- Provide migration examples for common usage patterns.
60+
- If possible, add deprecation warnings or migration helpers in your own APIs to ease the transition for your users.
61+
62+
## References
63+
- [graphql-java v24.0 Release Notes](https://github.com/graphql-java/graphql-java/releases/tag/v24.0)
64+
- [java-dataloader v4.0.0 Release Notes](https://github.com/graphql-java/java-dataloader/releases/tag/v4.0.0)
65+
66+
---
67+
68+
If you have further questions or encounter issues, please open an issue in the repository or consult the official release notes linked above.

buildSrc/src/main/kotlin/com.expediagroup.graphql.conventions.gradle.kts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@ tasks {
2323
}
2424
val kotlinJvmVersion: String by project
2525
withType<KotlinCompile> {
26-
kotlinOptions {
26+
compilerOptions {
2727
// intellij gets confused without it
28-
jvmTarget = kotlinJvmVersion
29-
freeCompilerArgs = listOf("-Xjsr305=strict")
28+
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.fromTarget(kotlinJvmVersion))
29+
freeCompilerArgs.set(listOf("-Xjsr305=strict"))
3030
}
3131
}
3232

clients/graphql-kotlin-client-jackson/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ tasks {
1616
limit {
1717
counter = "INSTRUCTION"
1818
value = "COVEREDRATIO"
19-
minimum = "0.85".toBigDecimal()
19+
minimum = "0.84".toBigDecimal()
2020
}
2121
}
2222
}

examples/buildSrc/src/main/kotlin/com.expediagroup.graphql.examples.conventions.gradle.kts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ dependencies {
2626
}
2727

2828
tasks.withType<KotlinCompile> {
29-
kotlinOptions {
30-
jvmTarget = "17"
31-
freeCompilerArgs = listOf("-Xjsr305=strict")
29+
compilerOptions {
30+
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17)
31+
freeCompilerArgs.set(listOf("-Xjsr305=strict"))
3232
}
3333
}
3434

examples/server/ktor-server/src/main/kotlin/com/expediagroup/graphql/examples/server/ktor/schema/dataloaders/BookDataLoader.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,15 @@ import com.expediagroup.graphql.dataloader.KotlinDataLoader
2121
import kotlinx.coroutines.runBlocking
2222
import graphql.GraphQLContext
2323
import org.dataloader.DataLoaderFactory
24+
import java.util.Optional
2425
import java.util.concurrent.CompletableFuture
2526

26-
val BookDataLoader = object : KotlinDataLoader<Int, Book?> {
27+
val BookDataLoader = object : KotlinDataLoader<Int, Optional<Book>> {
2728
override val dataLoaderName = "BOOK_LOADER"
2829
override fun getDataLoader(graphQLContext: GraphQLContext) =
2930
DataLoaderFactory.newDataLoader { ids ->
3031
CompletableFuture.supplyAsync {
31-
runBlocking { Book.search(ids).toMutableList() }
32+
runBlocking { Book.search(ids).map { Optional.ofNullable(it) }.toMutableList() }
3233
}
3334
}
3435
}

examples/server/ktor-server/src/main/kotlin/com/expediagroup/graphql/examples/server/ktor/schema/dataloaders/CourseDataLoader.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,15 @@ import com.expediagroup.graphql.dataloader.KotlinDataLoader
2121
import kotlinx.coroutines.runBlocking
2222
import graphql.GraphQLContext
2323
import org.dataloader.DataLoaderFactory
24+
import java.util.Optional
2425
import java.util.concurrent.CompletableFuture
2526

26-
val CourseDataLoader = object : KotlinDataLoader<Int, Course?> {
27+
val CourseDataLoader = object : KotlinDataLoader<Int, Optional<Course>> {
2728
override val dataLoaderName = "COURSE_LOADER"
2829
override fun getDataLoader(graphQLContext: GraphQLContext) =
2930
DataLoaderFactory.newDataLoader { ids ->
3031
CompletableFuture.supplyAsync {
31-
runBlocking { Course.search(ids).toMutableList() }
32+
runBlocking { Course.search(ids).map { Optional.ofNullable(it) }.toMutableList() }
3233
}
3334
}
3435
}

examples/server/ktor-server/src/main/kotlin/com/expediagroup/graphql/examples/server/ktor/schema/dataloaders/UniversityDataLoader.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,15 @@ import com.expediagroup.graphql.dataloader.KotlinDataLoader
2121
import kotlinx.coroutines.runBlocking
2222
import graphql.GraphQLContext
2323
import org.dataloader.DataLoaderFactory
24+
import java.util.Optional
2425
import java.util.concurrent.CompletableFuture
2526

26-
val UniversityDataLoader = object : KotlinDataLoader<Int, University?> {
27+
val UniversityDataLoader = object : KotlinDataLoader<Int, Optional<University>> {
2728
override val dataLoaderName = "UNIVERSITY_LOADER"
2829
override fun getDataLoader(graphQLContext: GraphQLContext) =
2930
DataLoaderFactory.newDataLoader { ids ->
3031
CompletableFuture.supplyAsync {
31-
runBlocking { University.search(ids).toMutableList() }
32+
runBlocking { University.search(ids).map { Optional.ofNullable(it) }.toMutableList() }
3233
}
3334
}
3435
}

executions/graphql-kotlin-dataloader-instrumentation/src/test/kotlin/com/expediagroup/graphql/dataloader/instrumentation/fixture/datafetcher/AstronautService.kt

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,19 @@ import java.util.concurrent.CompletableFuture
3737
data class AstronautServiceRequest(val id: Int)
3838
data class CreateAstronautServiceRequest(val name: String)
3939

40-
class AstronautDataLoader : KotlinDataLoader<AstronautServiceRequest, Astronaut?> {
40+
class AstronautDataLoader : KotlinDataLoader<AstronautServiceRequest, Optional<Astronaut>> {
4141
override val dataLoaderName: String = "AstronautDataLoader"
42-
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<AstronautServiceRequest, Astronaut?> =
42+
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<AstronautServiceRequest, Optional<Astronaut>> =
4343
DataLoaderFactory.newDataLoader(
44-
{ keys ->
44+
{ keys: List<AstronautServiceRequest> ->
4545
AstronautRepository
4646
.getAstronauts(keys.map(AstronautServiceRequest::id))
4747
.collectList()
48-
.map(List<Optional<Astronaut>>::toListOfNullables)
4948
.toFuture()
5049
},
51-
DataLoaderOptions.newOptions().setStatisticsCollector(::SimpleStatisticsCollector)
50+
DataLoaderOptions.newOptions()
51+
.setStatisticsCollector(::SimpleStatisticsCollector)
52+
.build()
5253
)
5354
}
5455

@@ -58,8 +59,10 @@ class AstronautService {
5859
environment: DataFetchingEnvironment
5960
): CompletableFuture<Astronaut> =
6061
environment
61-
.getDataLoader<AstronautServiceRequest, Astronaut>("AstronautDataLoader")
62-
?.load(request) ?: throw IllegalArgumentException("No data loader called AstronautDataLoader was found")
62+
.getDataLoader<AstronautServiceRequest, Optional<Astronaut>>("AstronautDataLoader")
63+
?.load(request)
64+
?.thenApply { it.orElse(null) }
65+
?: throw IllegalArgumentException("No data loader called AstronautDataLoader was found")
6366

6467
fun createAstronaut(
6568
request: CreateAstronautServiceRequest
@@ -72,8 +75,10 @@ class AstronautService {
7275
): CompletableFuture<List<Astronaut?>> = when {
7376
requests.isNotEmpty() -> {
7477
environment
75-
.getDataLoader<AstronautServiceRequest, Astronaut>("AstronautDataLoader")
76-
?.loadMany(requests) ?: throw IllegalArgumentException("No data loader called AstronautDataLoader was found")
78+
.getDataLoader<AstronautServiceRequest, Optional<Astronaut>>("AstronautDataLoader")
79+
?.loadMany(requests)
80+
?.thenApply { optionals -> optionals.map { it.orElse(null) } }
81+
?: throw IllegalArgumentException("No data loader called AstronautDataLoader was found")
7782
}
7883
else -> {
7984
AstronautRepository

executions/graphql-kotlin-dataloader-instrumentation/src/test/kotlin/com/expediagroup/graphql/dataloader/instrumentation/fixture/datafetcher/MissionService.kt

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,31 +31,34 @@ import java.util.concurrent.CompletableFuture
3131

3232
data class MissionServiceRequest(val id: Int, val astronautId: Int = -1)
3333

34-
class MissionDataLoader : KotlinDataLoader<MissionServiceRequest, Mission?> {
34+
class MissionDataLoader : KotlinDataLoader<MissionServiceRequest, Optional<Mission>> {
3535
override val dataLoaderName: String = "MissionDataLoader"
36-
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<MissionServiceRequest, Mission?> =
36+
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<MissionServiceRequest, Optional<Mission>> =
3737
DataLoaderFactory.newDataLoader(
38-
{ keys ->
38+
{ keys: List<MissionServiceRequest> ->
3939
MissionRepository
4040
.getMissions(keys.map(MissionServiceRequest::id))
4141
.collectList()
42-
.map(List<Optional<Mission>>::toListOfNullables)
4342
.toFuture()
4443
},
45-
DataLoaderOptions.newOptions().setStatisticsCollector(::SimpleStatisticsCollector)
44+
DataLoaderOptions.newOptions()
45+
.setStatisticsCollector(::SimpleStatisticsCollector)
46+
.build()
4647
)
4748
}
4849

4950
class MissionsByAstronautDataLoader : KotlinDataLoader<MissionServiceRequest, List<Mission>> {
5051
override val dataLoaderName: String = "MissionsByAstronautDataLoader"
5152
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<MissionServiceRequest, List<Mission>> =
5253
DataLoaderFactory.newDataLoader(
53-
{ keys ->
54+
{ keys: List<MissionServiceRequest> ->
5455
MissionRepository
5556
.getMissionsByAstronautIds(keys.map(MissionServiceRequest::astronautId))
5657
.collectList().toFuture()
5758
},
58-
DataLoaderOptions.newOptions().setStatisticsCollector(::SimpleStatisticsCollector)
59+
DataLoaderOptions.newOptions()
60+
.setStatisticsCollector(::SimpleStatisticsCollector)
61+
.build()
5962
)
6063
}
6164

@@ -65,17 +68,21 @@ class MissionService {
6568
environment: DataFetchingEnvironment
6669
): CompletableFuture<Mission> =
6770
environment
68-
.getDataLoader<MissionServiceRequest, Mission>("MissionDataLoader")
69-
?.load(request) ?: throw IllegalArgumentException("No data loader called MissionDataLoader was found")
71+
.getDataLoader<MissionServiceRequest, Optional<Mission>>("MissionDataLoader")
72+
?.load(request)
73+
?.thenApply { it.orElse(null) }
74+
?: throw IllegalArgumentException("No data loader called MissionDataLoader was found")
7075

7176
fun getMissions(
7277
requests: List<MissionServiceRequest>,
7378
environment: DataFetchingEnvironment
7479
): CompletableFuture<List<Mission?>> = when {
7580
requests.isNotEmpty() -> {
7681
environment
77-
.getDataLoader<MissionServiceRequest, Mission>("MissionDataLoader")
78-
?.loadMany(requests) ?: throw IllegalArgumentException("No data loader called MissionDataLoader was found")
82+
.getDataLoader<MissionServiceRequest, Optional<Mission>>("MissionDataLoader")
83+
?.loadMany(requests)
84+
?.thenApply { optionals -> optionals.map { it.orElse(null) } }
85+
?: throw IllegalArgumentException("No data loader called MissionDataLoader was found")
7986
}
8087
else -> {
8188
MissionRepository

executions/graphql-kotlin-dataloader-instrumentation/src/test/kotlin/com/expediagroup/graphql/dataloader/instrumentation/fixture/datafetcher/PlanetService.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,15 @@ class PlanetsByMissionDataLoader : KotlinDataLoader<PlanetServiceRequest, List<P
3333
override val dataLoaderName: String = "PlanetsByMissionDataLoader"
3434
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<PlanetServiceRequest, List<Planet>> =
3535
DataLoaderFactory.newDataLoader(
36-
{ keys ->
36+
{ keys: List<PlanetServiceRequest> ->
3737
PlanetRepository
3838
.getPlanetsByMissionIds(keys.map(PlanetServiceRequest::missionId))
3939
.collectList()
4040
.toFuture()
4141
},
42-
DataLoaderOptions.newOptions().setStatisticsCollector(::SimpleStatisticsCollector)
42+
DataLoaderOptions.newOptions()
43+
.setStatisticsCollector(::SimpleStatisticsCollector)
44+
.build()
4345
)
4446
}
4547

0 commit comments

Comments
 (0)