i'm working on a kotlin spring boot webflux app and decided to use kotlinx.serialization for serialization of some custom types. my custom serialization actually seems to work fine. but to my surprise, webflux is not using kotlinx.serialization to serialize my server responses at all ;-;
this is my handler:
fun postVideo(body: Mono<List<ApiVideo>>): Mono<out ServerResponse> {
return body
.map { videos -> videos.map { it.toDoc(VidId.random()) } }
.flux()
.flatMap { videoRepo.insert(it) }
.map { it.toApi() }
.collectList()
.flatMap{ ok(it) }
}
fun <T> ok(body: T) = ServerResponse.ok().json().bodyValue(dataResponse(body))
response envelope:
import kotlinx.serialization.Serializable
@Serializable
data class ResponseEnvelope<T>(
val data: T? = null,
val errors: List<String>? = null,
)
fun <T> dataResponse(data: T?): ResponseEnvelope<T> = ResponseEnvelope(data = data)
fun <T> errorResponse(vararg errors: String): ResponseEnvelope<T> = ResponseEnvelope(errors = errors.toList())
router:
@Bean
fun videoRoutes(videoHandler: VideoHandler) = router {
path("/api/v1").nest {
path("/videos").nest {
POST { req -> videoHandler.postVideo(req.bodyToMono()) }
path("/{id}").nest {
GET { req -> videoHandler.getVideo(req.pathVariable("id")) }
PUT { req -> videoHandler.putVideo(req.pathVariable("id"), req.bodyToMono()) }
}
}
}
}
and build.gradle.kts:
plugins {
id("io.spring.dependency-management") version "1.1.0"
id("org.springframework.boot") version "3.1.1"
kotlin("jvm") version "1.9.21"
kotlin("plugin.spring") version "1.9.21"
kotlin("plugin.serialization") version "1.9.21"
}
group = "com.example"
version = "0.0.1-SNAPSHOT"
repositories {
mavenCentral()
}
kotlin {
jvmToolchain {
languageVersion.set(JavaLanguageVersion.of(17))
}
}
dependencies {
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
implementation("io.projectreactor.kotlin:reactor-kotlin-extensions")
implementation("org.apache.kafka:kafka-streams")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.6.2")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2")
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("org.springframework.boot:spring-boot-starter-data-mongodb-reactive")
implementation("org.springframework.boot:spring-boot-starter-webflux")
developmentOnly("org.springframework.boot:spring-boot-devtools")
developmentOnly("org.springframework.boot:spring-boot-docker-compose")
}
making a request results in a response like this:
{
"data": [
{
"id": {
"group": "53",
"name": "fef99e9e30431dbe9ae308dea17ab6",
"path": "53/fef99e9e30431dbe9ae308dea17ab6"
},
"title": "a vid",
}
],
"errors": null
}
which is incorrect, as the id serializer should produce "id": "53fef99e9e30431dbe9ae308dea17ab6". but by debugging, i found out that no kotlinx serialization is happening at all (not just that it's not calling my custom one). the logic in webflux that picks a serializer is concluding that there is no kotlinx serializer for the response envelope, and that jackson does support it, so it uses jackson instead.
more specifically, i traced the response handling to org.springframework.http.codec.KotlinSerializationSupport#serializer, which webflux calls to find a compatible serializer, and that function is returning null. not for my id type, but for stuff like List and ResponseEnvelope
i tried returning a List instead of a response envelope, but that made no difference. i feel like i'm losing my mind. that i must be missing something so simple, like a config i don't realize i'm missing. how could it not find a serializer for a basic data class, or a list???