niklasfi
niklasfi

Reputation: 15951

Spring-Boot: Jackson serialization configuration not respected

I am working with spring-boot 2.5.4 and trying to set default-property-inclusion=non_null globally, so that null values no longer show up in my spring web server's responses.

I have configured spring.jackson.default-property-inclusion=non_null in my project's application.yml as described in an earlier question.

spring:
  jackson:
    default-property-inclusion: non_null

Unfortunately, null-values are still included in my controller's output:

{
    "enabled": true,
    "name": "foo",
    "subdomain": null,
    "tenantId": null
}

What works however is if I add non-null directly to my dto:

@Data
@NoArgsConstructor
@JsonInclude(Include.NON_NULL)
public class GlobalTenantGet {

    private UUID tenantId;

    private String name;

    private String subdomain;

    private boolean enabled;
}

yielding

{
    "enabled": true,
    "name": "foo"
}

as expected.

question

Why does configuring @JsonInclude locally on the dto cause the null properties to disappear as expected but why does configuring spring.jackson.default-property-inclusion=non_null not have the same effect?

additional info

1. Debugging

Setting a breakpoint in org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration.JacksonObjectMapperConfiguration#jacksonObjectMapper shows, that the properties configured in application.yaml really made it to jackson:

serializationInclusion

2. Autowiring ObjectMapper in the controller

I get the expected behavior when autowiring the ObjectMapper in my controller and serializing my object directly via objectMapper.writeValueAsString().

other ways to configure the ObjectMapper

These are mostly in reply to Pawel Woroniecki's answer

1. Using @primary

I have a

@Configuration
public class ObjectMapperConfiguration {

    @Primary
    @Bean
    public ObjectMapper objectMapper() {
        final var om = new ObjectMapper();
        om.setSerializationInclusion(Include.NON_NULL);
        return om;
    }
}

still includes null properties in the output

2. Jackson2ObjectMapperBuilder with serializationInclusion

@Configuration
public class ObjectMapperConfiguration {
    @Bean
    public Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder() {
        return new Jackson2ObjectMapperBuilder().serializationInclusion(JsonInclude.Include.NON_NULL);
    }
}

still yields

{
    "enabled": true,
    "name": "foo",
    "subdomain": null,
    "tenantId": null
}

3. define MappingJackson2HttpMessageConverter

@Configuration
public class ObjectMapperConfiguration {
    @Bean
    public MappingJackson2HttpMessageConverter messageMappingConverter() {
        final var om = new ObjectMapper();
        om.setSerializationInclusion(Include.NON_NULL);
        return new MappingJackson2HttpMessageConverter(om);
    }
}

still no bueno :-(

Upvotes: 3

Views: 5233

Answers (2)

niklasfi
niklasfi

Reputation: 15951

It seems @EnableWebMvc is the culprit. I have a WebMvcConfigurer that looks like this:

@Configuration
@EnableWebMvc // <----- this is the culprit!
public class WebConfiguration implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedMethods("OPTIONS", "GET", "POST", "PUT", "PATCH", "DELETE")
                .allowedOrigins("example.com")
                .allowedHeaders("*");
    }

    @Bean
    public KeycloakSpringBootConfigResolver KeycloakConfigResolver() {
        return new KeycloakSpringBootConfigResolver();
    }

    @Bean
    public InternalResourceViewResolver defaultViewResolver() {
        return new InternalResourceViewResolver();
    }

}

commenting out @EnableWebMvc allows everything to work as expected.

Upvotes: 9

Pawel Woroniecki
Pawel Woroniecki

Reputation: 519

There are several possible approaches to solve this issue and some of them are clearly explained here: https://www.baeldung.com/spring-boot-customize-jackson-objectmapper

Generally you can:

  1. Override default ObjectMapper by creating custom ObjectMapper bean and marking it as @Primary (the simplest way but heavily impacts your application as all ObjectMapper instances you inject in code will use this one by default which may be good or not for you)

  2. Define custom bean for Jackson2ObjectMapperBuilder and set proper serializationInclusion on this builder.

  3. Register custom HTTP message converter in Spring which will use custom ObjectMapper by defining bean for MappingJackson2HttpMessageConverter (constructor of this converter accepts ObjectMapper so you can pass properly configured mapper there)

Upvotes: 1

Related Questions