Over the years, the combined usage of Kotlin Serialization alongside another general purpose JSON library (typically Jackson) has triggered the creation of a lot of bug reports in Spring Framework (#26298, #26371, #26147, #33138, #33528, #33016, #32382, #28856) and Spring Boot (spring-projects/spring-boot#24238, spring-projects/spring-boot#47178).
Several refinements were shipped as part of Spring Framework 7 milestones and RCs to refine the ordering and configuration (#34410, #34433, #35733) but we still see some unwanted side effects.
I think we are now at a point where we realize that those side effects can't be solved without changing how Kotlin Serialization converters/codecs implement canRead / canWrite methods.
One of the reasons is that Kotlin Serialization is not a general purpose JSON serialization library, it is designed to handle mostly classes annotated with @Serializable, but also some other cases like:
- unannotated Kotlin enums
- Containers shared between Java and Kotlin like List, Map, etc.
- Numbers, characters, booleans and strings
Earlier in Spring Framework 7, there was an attempt to solve that by considering Kotlin Serialization like other JSON libraries and disallow side by side usage of multiple ones, but in practice that does not work since Jackson is still needed for some Java infrastructure like Spring Boot actuators or Spring REST Docs, potentially in the same application that requires using Kotlin Serialization to serialize Kotlin classes annotated with @Serialiable. That means we don't have the choice, Spring needs to support side by side Kotlin Serialization and Jackson in that order of evaluation.
To avoid Kotlin Serialization being used instead of Jackson when that's not relevant, we need to evolve Kotlin Serialization converters/codecs to be more specific. This issue intends to introduce a KotlinDetector#hasSerializableAnnotation method that will be used to identify types annotated with @Serializable at class or generic type level, and to change Kotlin Serialization converters/codecs canRead / canWrite method to return true only when the type has a @Serializable annotation, before still testing if a related serializer is found. This will be the behavior of the default constructor (breaking change) and the one used by Spring Framework and Spring Boot when both Kotlin Serialization and Jackson/GSON support are activated side by side.
When only Kotlin serialization is activated (for example when Jackson/GSON are not in the classpath), no preliminary @Serializable check will be performed, and the previous behavior will be used (check if Kotlin Serialization has a related serializer) in order to be able to serialize types like String, List<String>, numbers, characters, booleans, strings and enums.
It will also be possible to provide a custom type predicate to refine the canRead / canWrite behavior.
It is not ideal to do such change late in the cycle before the GA, but that's better than after, and we had to take in account users feedback on RCs to come to the conclusion there is not other way to remove those side effects. The upgrade notes will be updated to mention this change and provide guidance for migrating easily. This new default behavior should be more predictable, easier to understand, stop side effects and still allow flexible customization if needed.