I am using Java 17, spring-boot 2.6.3 with spring-webflux and spring-consul dependencies and I have the following class:
import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; import org.springframework.context.annotation.Primary; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import org.springframework.web.reactive.config.EnableWebFlux; import com.enterprise.project.model.serializer.ModelSerializer; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.ObjectMapper; @SpringBootApplication @EnableWebFlux public class Application { public static void main(final String[] args) { SpringApplication.run(Application.class, args); } @Bean static BeanFactoryPostProcessor beanFactoryPostProcessor(final ApplicationContext beanRegistry) { return (final var beanFactory) -> { final var beanDefinitionScanner = new ClassPathBeanDefinitionScanner( (BeanDefinitionRegistry) ((AnnotationConfigReactiveWebServerApplicationContext) beanRegistry) .getBeanFactory()); beanDefinitionScanner.addIncludeFilter( (final var mr, final var mrf) -> !mr.getClassMetadata().getClassName().contains("model")); beanDefinitionScanner.scan("com.enterprise.project", "com.enterprise.project.model.serializer"); }; } @Bean @Primary public ObjectMapper customObjectMapper(final Jackson2ObjectMapperBuilder builder) { return builder.serializationInclusion(JsonInclude.Include.NON_NULL).serializers(new ModelSerializer()).build(); } }
Here’s my custom serializer.
import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Base64; import com.enterprise.project.model.FirstModel; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.ser.std.StdSerializer; import com.fasterxml.jackson.dataformat.xml.XmlMapper; public class ModelSerializer extends StdSerializer<FirstModel> { private static final long serialVersionUID = -5754330070183741830L; public ModelSerializer() { this(FirstModel.class); } public ModelSerializer(final Class<FirstModel> responseModel) { super(responseModel); } @Override public void serialize(final FirstModel Model, final JsonGenerator jgen, @SuppressWarnings("unused") final SerializerProvider provider) throws IOException { jgen.writeStartObject(); jgen.writeNumberField("one", Model.second()); final var totalValue = Model.third(); if (totalValue != null) { jgen.writeNumberField("two", totalValue); } jgen.writeNumberField("three", Model.first().intValue()); jgen.writeStringField("four", Model.fourth()); final var surcharge = Model.fifth(); if (surcharge != null) { jgen.writeBooleanField("five", surcharge.booleanValue()); } jgen.writeStringField("six", ModelSerializer.encodeBase64(ModelSerializer.toXML(Model.sixth()))); jgen.writeEndObject(); } public static String encodeBase64(final String toEncode) { return Base64.getEncoder().encodeToString(toEncode.getBytes(StandardCharsets.UTF_8)); } public static <T> String toXML(final T data) { try { return XmlMapper.builder().build().writeValueAsString(data); } catch (@SuppressWarnings("unused") final JsonProcessingException e) { return ""; } } }
And my models are as simples as this:
import java.math.BigDecimal; import com.fasterxml.jackson.annotation.JsonInclude; @JsonInclude(JsonInclude.Include.NON_NULL) public record FirstModel(Integer first, BigDecimal second, BigDecimal third, String fourth, Boolean fifth, SecondModel sixth) { // In most cases record classes does not need any implementation. } import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; @JacksonXmlRootElement(localName = "second") @JsonInclude(JsonInclude.Include.NON_NULL) public record SecondModel(@JacksonXmlProperty(isAttribute = true) String first, @JacksonXmlProperty(isAttribute = true) String second, @JacksonXmlProperty(isAttribute = true) String third, @JacksonXmlProperty(isAttribute = true) String fourth, @JacksonXmlProperty(isAttribute = true) String fifth, @JacksonXmlProperty(isAttribute = true) String sixth, @JacksonXmlProperty(isAttribute = true) String seventh, @JacksonXmlProperty(isAttribute = true) String eighth, @JacksonXmlProperty(isAttribute = true) String ninth, @JacksonXmlProperty(isAttribute = true) String tenth, @JacksonXmlProperty(isAttribute = true) String eleventh, @JacksonXmlProperty(isAttribute = true) ThirdModel twelfth) { // In most cases record classes does not need any implementation. } import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; @JacksonXmlRootElement(localName = "third") @JsonInclude(JsonInclude.Include.NON_NULL) public record ThirdModel(@JacksonXmlProperty(isAttribute = true) String first, @JacksonXmlProperty(isAttribute = true) String second, @JacksonXmlProperty(isAttribute = true) String third, @JacksonXmlProperty(isAttribute = true) String fourth, @JacksonXmlProperty(isAttribute = true) String fifth) { // In most cases record classes does not need any implementation. }
I can’t annotate my model class with @JsonSerialize(using = ModelSerializer.class)
because it is in a dependency that I am not allowed to modify. I know I did wrote the code models here, but I am only allowed read-only permission in this repository.
Somehow the ModelSerializer
never gets called even though my actuator/beans
and actuator/conditions
endpoints report the customObjectMapper as a bean. Any ideas why? Do I need any other specifics jackson dependencies?
Here’s my dependency tree with jackson dependencies:
com.enterprise.project:spring-webflux-custom-objectmapper:jar:1.0.0 +- org.springframework.boot:spring-boot-starter-actuator:jar:2.6.3:compile | +- org.springframework.boot:spring-boot-starter:jar:2.6.3:compile | | +- org.springframework.boot:spring-boot:jar:2.6.3:compile | | | - org.springframework:spring-context:jar:5.3.15:compile | | | +- org.springframework:spring-aop:jar:5.3.15:compile | | | - org.springframework:spring-expression:jar:5.3.15:compile | | +- org.springframework.boot:spring-boot-autoconfigure:jar:2.6.3:compile | | +- org.springframework.boot:spring-boot-starter-logging:jar:2.6.3:compile | | | +- ch.qos.logback:logback-classic:jar:1.2.10:compile | | | | +- ch.qos.logback:logback-core:jar:1.2.10:compile | | | | - org.slf4j:slf4j-api:jar:1.7.33:compile | | | +- org.apache.logging.log4j:log4j-to-slf4j:jar:2.17.1:compile | | | | - org.apache.logging.log4j:log4j-api:jar:2.17.1:compile | | | - org.slf4j:jul-to-slf4j:jar:1.7.33:compile | | +- jakarta.annotation:jakarta.annotation-api:jar:1.3.5:compile | | +- org.springframework:spring-core:jar:5.3.15:compile | | | - org.springframework:spring-jcl:jar:5.3.15:compile | | - org.yaml:snakeyaml:jar:1.29:compile | +- org.springframework.boot:spring-boot-actuator-autoconfigure:jar:2.6.3:compile | | +- org.springframework.boot:spring-boot-actuator:jar:2.6.3:compile | | - com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:2.13.1:compile | - io.micrometer:micrometer-core:jar:1.8.2:compile | +- org.hdrhistogram:HdrHistogram:jar:2.1.12:compile | - org.latencyutils:LatencyUtils:jar:2.0.3:runtime +- org.springframework.boot:spring-boot-starter-webflux:jar:2.6.3:compile | +- org.springframework.boot:spring-boot-starter-json:jar:2.6.3:compile | | +- com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:2.13.1:compile | | - com.fasterxml.jackson.module:jackson-module-parameter-names:jar:2.13.1:compile | +- org.springframework.boot:spring-boot-starter-reactor-netty:jar:2.6.3:compile | | - io.projectreactor.netty:reactor-netty-http:jar:1.0.15:compile | | +- io.netty:netty-codec-http:jar:4.1.73.Final:compile | | | +- io.netty:netty-common:jar:4.1.73.Final:compile | | | +- io.netty:netty-buffer:jar:4.1.73.Final:compile | | | +- io.netty:netty-transport:jar:4.1.73.Final:compile | | | +- io.netty:netty-codec:jar:4.1.73.Final:compile | | | - io.netty:netty-handler:jar:4.1.73.Final:compile | | | - io.netty:netty-tcnative-classes:jar:2.0.46.Final:compile | | +- io.netty:netty-codec-http2:jar:4.1.73.Final:compile | | +- io.netty:netty-resolver-dns:jar:4.1.73.Final:compile | | | +- io.netty:netty-resolver:jar:4.1.73.Final:compile | | | - io.netty:netty-codec-dns:jar:4.1.73.Final:compile | | +- io.netty:netty-resolver-dns-native-macos:jar:osx-x86_64:4.1.73.Final:compile | | | - io.netty:netty-resolver-dns-classes-macos:jar:4.1.73.Final:compile | | +- io.netty:netty-transport-native-epoll:jar:linux-x86_64:4.1.73.Final:compile | | | +- io.netty:netty-transport-native-unix-common:jar:4.1.73.Final:compile | | | - io.netty:netty-transport-classes-epoll:jar:4.1.73.Final:compile | | - io.projectreactor.netty:reactor-netty-core:jar:1.0.15:compile | | - io.netty:netty-handler-proxy:jar:4.1.73.Final:compile | | - io.netty:netty-codec-socks:jar:4.1.73.Final:compile | +- org.springframework:spring-web:jar:5.3.15:compile | | - org.springframework:spring-beans:jar:5.3.15:compile | - org.springframework:spring-webflux:jar:5.3.15:compile | - io.projectreactor:reactor-core:jar:3.4.14:compile | - org.reactivestreams:reactive-streams:jar:1.0.3:compile - com.fasterxml.jackson.dataformat:jackson-dataformat-xml:jar:2.13.1:compile +- com.fasterxml.jackson.core:jackson-core:jar:2.13.1:compile +- com.fasterxml.jackson.core:jackson-annotations:jar:2.13.1:compile +- com.fasterxml.jackson.core:jackson-databind:jar:2.13.1:compile +- org.codehaus.woodstox:stax2-api:jar:4.2.1:compile - com.fasterxml.woodstox:woodstox-core:jar:6.2.7:compile
I also have put the code into a git repository to facilitate if someone can help me out and a issue report into spring-boot repository.
Advertisement
Answer
You have annotated your application with @EnableWebFlux
. This indicates that you want to take complete control of WebFlux’s configuration. This causes Spring Boot’s auto-configuration of WebFlux to back off. Among other things, this means that it won’t configure WebFlux to use the context’s ObjectMapper
.
You should either remove @EnableWebFlux
to allow Spring Boot to auto-configure WebFlux or you should configure its codecs manually so that they use your ObjectMapper
.