Kotlinx serialization with Spring Boot

Has anyone tried this? The code that works great with KTOR rest seems to not work correctly on the spring boot side. I am guessing spring-boot is just using Jackson by default. What verbiage can i search for to figure out how to override that and use Kotlin serialization instead of Jackson.

We have used kotlinx.serialization with some (experimental) spring-boot apps.

I have uploaded the code to GitHub - markt-de/spring-kotlinx-serialization, but there is no README yet.
It only supports WebApplicationType.REACTIVE apps so far, but those can be easily set up by applying the webflux-starter dependency:

implementation("de.classyfi.boot:spring-kotlinx-serialization-starter-webflux:0.0.1-20200318")

I have published a snapshot to JFrog Distribution: Get your software to where it needs to be -- ASAP! in case you want to try it.

how would i set up the repo on this?
I am dying to try it, want to use sealed classes as rest repsonses

This should do the trick:

repositories {
  jcenter()
  maven { url = uri("https://dl.bintray.com/markt-de/snapshots/") }
}

dependencies {
  implementation("de.classyfi.boot:spring-kotlinx-serialization-starter-webflux:0.0.1-20200318")
}

I’m looking forward to your feedback!

i cant wait, after using kotlinx serialization on ktor, i am missing in on springboot

I was able to remove the dependency on com.fasterxml.jackson.module:jackson-module-kotlin:2.10.3 and the project still worked.

trying it out in a kotlin multi-platform project
the following code still just pushing out the success portion of the sealed class

@PostMapping(STUB_SEALED_TEST)
fun testSealed():TestSealedClass<List<PrintShopDTO>>{
    val all = repo.findAll().map { it.toDto() }
    return TestSealedClass.Success(all)
}

her is the sealed class

@Serializable
sealed class TestSealedClass <out T>{
    @Serializable
    data class Failure<out T>(val msg: String) : TestSealedClass<T>()
    @Serializable
    data class Success<out T>(val payload: T) :  TestSealedClass<T>()
}

and here is the js side ingestion

suspend fun testSealed() {
    val url ="$REST_SERVICE_ADDRESS/${CTX_PRINT_SHOP.getStub()}/$STUB_SEALED_TEST"
    val response = makeXMLHttpRequest(url,"", HttpMethod.POST, CTX_PRINT_SHOP, "testSealed")
    console.log(response)
    val parsed = Json.parse(deserializer = TestSealedClass.serializer(typeSerial0 = PrintShopDTO.serializer().list), string = response)
    console.log(parsed)

}

the browser side error on parse

JsonDecodingException: Invalid JSON at 0: Expected '[, kind: SEALED

the string data looks like this:

{“payload”:[{“id”:1,“dateCreated”:{“year”:2020,“month”:2,“day”:24,“hour”:10,“minute”:29,“second”:0,“milliSecond”:0,“jdTime”:217249350},“dateUpdated”:{“year”:2020,“month”:2,“day”:24,“hour”:10,“minute”:29,“second”:0,“milliSecond”:0,“jdTime”:217249350},“title”:“Everyday havent fam air chic hexagon fam truck mi unicorn bottle selfies cray moon humblebrag.”,“books”:[{“id”:2,“dateCreated”:{“year”:2020,“month”:2,“day”:24,“hour”:10,“minute”:29,“second”:0,“milliSecond”:0,“jdTime”:217249418},“dateUpdated”:{“year”:2020,“month”:2,“day”:24,“hour”:10,“minute”:29,“second”:0,“milliSecond”:0,“jdTime”:217249418},"d …

using the 0.14.0 version of kotlinx serialization

going to test some non sealed class stuff in a bit

oddly i am still getting jackson errors - i must be doing something wrong… Project builds. Build.gradle

jvmMain {
    dependencies {
        implementation kotlin('stdlib-jdk8')
        implementation("org.jetbrains.kotlinx:kotlinx-serialization-runtime:$v_serialization")
        implementation 'de.classyfi.libs:spring-kotlinx-serialization:0.0.1-20200318'
        //implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.10.3")
        implementation("org.springframework.boot:spring-boot-starter-web")
        implementation("org.springframework.boot:spring-boot-starter-data-jpa")
        implementation("org.springframework.boot:spring-boot-starter-websocket")
        implementation("org.jetbrains.kotlin:kotlin-reflect")
        runtimeOnly("com.h2database:h2")
    }
}

my classes

@Serializable
abstract class TDetail

@Serializable
data class AdminDetail(val alergic:Boolean) : TDetail()

@Serializable
data class CustomerDetail(val dogName:String) : TDetail()

@Serializable
data class TSecUserREQ(
    val fname:String,
    val detail: TDetail
)

val simplePolymorphicModule = SerializersModule {
    polymorphic<TDetail> {
        AdminDetail::class with AdminDetail.serializer()
        CustomerDetail::class with CustomerDetail.serializer()
    }
}

sending it too spring via

suspend fun testDeep(){
    val json = Json(context = simplePolymorphicModule)
    val user = TSecUserREQ(fname = "Chris", detail = CustomerDetail(dogName = "buddy"))
    val userSerial = json.stringify(TSecUserREQ.serializer(), user)
    val url ="$REST_SERVICE_ADDRESS/${CTX_SEC_USER.getStub()}"
    val response = makeXMLHttpRequest(url,userSerial, HttpMethod.POST, CTX_PRINT_SHOP, "testDeep")
}

the error :com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of net.ideablender.foundation.req.TSecUserREQ (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)

As I said, the library so far only supports reactive webapps (spring-boot-starter-webflux). Servlet apps (spring-boot-starter-web) are currently not supported.

Pull requests are of course more than welcome.

ty for the project, i need to read up more on webflux

Hello everyone. So I am trying to get my non-reactive Spring Boot app to work with kotlinx serialization and I am having major issues, one of which is that all my requests are being rejected!

As soon as I go back to Jackson, everything is fine.

Anyone got any ideas or tried to use kotlinx with Spring Boot (spring mvc)?

Spring Framework supports Kotlinx Serialization starting from version 5.3 (which is what Spring Boot 2.4 uses). See this for more info.

3 Likes

I confirm, I’m using it with spring boot 2.4 and it works really well.

I’m fighting to make it work with Spring Boot 2.4.1 with no luck. Could you please share your Gradle/Maven configuration and/or any special steps?

1 Like

Look at a quick demo I made here: GitHub - bjonnh/spring-kotlin-serializer-demo

As far as I can see, Spring Boot requires all fields in data class to have default values. Is this correct?

Edit: excluding Jackson from classpath in GitHub - bjonnh/spring-kotlin-serializer-demo forced Spring to use kotlinx-serializer and thus allowed data fields to allow non-default values. If you need Jackson on classpath you can force RestTemplate to use kotlinx-serializer by adding following:

RestTemplateBuilder()
  .messageConverters(KotlinSerializationJsonHttpMessageConverter())
  .build()
1 Like

I managed to switch to Kotlin serialization using Spring Boot, but is there a way to specify a custom lenient Json parser? For example I’m used to using a Json parser like this:

val jsonNonStrict = Json {
isLenient = true
ignoreUnknownKeys = true
allowSpecialFloatingPointValues = true
useArrayPolymorphism = true
}

I’m trying to figure out how to get my controllers to use that when parsing request bodies.

1 Like

@mike.423312:
Try this approach:

object JsonParser {
    val json = Json {
        ignoreUnknownKeys = true
        isLenient = true
        allowSpecialFloatingPointValues = true
        useArrayPolymorphism = true
        encodeDefaults = true // might need this also
    }
}

Then override extendMessageConverters() in WebMvcConfigurer, like:

@Configuration
class ConfigureMessageConverters() : WebMvcConfigurer {

    override fun extendMessageConverters(converters: MutableList<HttpMessageConverter<*>>) {
        val converter = KotlinSerializationJsonHttpMessageConverter(JsonParser.json)
        converters.forEachIndexed { index, httpMessageConverter ->
            if (httpMessageConverter is KotlinSerializationJsonHttpMessageConverter) {
                converters[index] = converter
                return
            }
        }
    }
}

Note:
for anyone who is getting HttpMediaTypeNotAcceptableException (Status 406): Make sure you are not excluding spring-boot-starter-json like in previous examples:

implementation("org.springframework.boot:spring-boot-starter-web"){
        exclude(module = "spring-boot-starter-json")  // remove this!!
}
1 Like