Spring Reactive Web uses kotlin serialization over jackson for its own classes, and it's very hard to change
Affects: Spring 5.3.21
This is somewhat related to #26321 - Spring using kotlin serialization over jackson (but not the same, as that's for WebMVC).
In Reactive Web, Kotlin classes that are tagged as @Serializable use Kotlin Serializers, not Jackson.
This is a reasonable default, but changing the behaviour is very difficult, and has a few surprising side effects.
In Spring WebMVC, we could re-order the HttpMessageConverter<>, and put Jackson first:
// The approach we've used for WebMVC - there's no analogus support in WebFlux. @Configuration class WebConfig : WebMvcConfigurationSupport() { override fun configureMessageConverters(converters: MutableList<HttpMessageConverter<*>?>) { super.addDefaultHttpMessageConverters(converters) converters.sortBy { converter -> if (converter is KotlinSerializationJsonHttpMessageConverter) 1000 else 0 } } }
The contract of WebFluxConfigurer doesn't allow modification of the list - because the BaseCodecConfigurer returns a new list each time:
@Override
public List<HttpMessageWriter<?>> getWriters() {
this.defaultCodecs.applyDefaultConfig(this.customCodecs);
List<HttpMessageWriter<?>> result = new ArrayList<>();
result.addAll(this.customCodecs.getTypedWriters().keySet());
result.addAll(this.defaultCodecs.getTypedWriters());
result.addAll(this.customCodecs.getObjectWriters().keySet());
result.addAll(this.defaultCodecs.getObjectWriters());
result.addAll(this.defaultCodecs.getCatchAllWriters());
return result;
}
Therefore, adding any sort like in WebMVC has no effect.
Changing the Kotlin encoder to null (to try to disable), doesn't work, as BaseDefaultCodecs simply adds it back:
@Override
public void kotlinSerializationJsonEncoder(Encoder<?> encoder) {
this.kotlinSerializationJsonEncoder = encoder;
initObjectWriters(); // triggers a call to getBaseObjectWriters()
}
final List<HttpMessageWriter<?>> getBaseObjectWriters() {
List<HttpMessageWriter<?>> writers = new ArrayList<>();
if (kotlinSerializationJsonPresent) {
addCodec(writers, new EncoderHttpMessageWriter<>(getKotlinSerializationJsonEncoder()));
}
...snip...
return writers;
}
The workaround I've used is to put a decorator around the configurer to re-order every single time. However, this seems awkward.
@Configuration class CustomerWebFluxConfigSupport : WebFluxConfigurationSupport() { override fun serverCodecConfigurer(): ServerCodecConfigurer { return ReOrderingServerCodecConfigurer(super.serverCodecConfigurer()) } class ReOrderingServerCodecConfigurer(private val configurer: ServerCodecConfigurer) : ServerCodecConfigurer by configurer { override fun getWriters(): MutableList<HttpMessageWriter<*>> { val writers = configurer.writers val jacksonWriterIndex = configurer.writers.indexOfFirst { it is EncoderHttpMessageWriter && it.encoder is Jackson2JsonEncoder } val kotlinSerializationWriterIndex = configurer.writers.indexOfFirst { it is EncoderHttpMessageWriter && it.encoder is KotlinSerializationJsonEncoder } if (kotlinSerializationWriterIndex == -1 || jacksonWriterIndex == -1) { return writers } if (kotlinSerializationWriterIndex < jacksonWriterIndex) { Collections.swap(writers, jacksonWriterIndex, kotlinSerializationWriterIndex) } return writers } } }
Expected / Desired Behaviour
It'd be nice if there was an easier way to configure this.
At the very least, where BaseDefaultCodecs overwrites the changed Kotlin serializer feels like a bug.