Custom ObjectMapper with Spring RestClient (migrating from WebClient)
Asked Answered
S

2

7

Currently using Spring Boot 3.1 with the reactive WebClient configured like this:

@Configuration
public class MyConfig {

  @Bean
  WebClient webClient() {
    ExchangeStrategies strategies = ExchangeStrategies.builder().codecs(clientCodecConfigurer ->
        {
          ObjectMapper objectMapper = createObjectMapper();
          Jackson2JsonDecoder decoder = new Jackson2JsonDecoder(objectMapper);
          decoder.setMaxInMemorySize(10_000_000);
          clientCodecConfigurer.customCodecs().register(decoder);
          clientCodecConfigurer.customCodecs().register(new Jackson2JsonEncoder(objectMapper));
        }
    ).build();

    return WebClient.builder()
        .exchangeStrategies(strategies)
        .baseUrl(this.properties.baseUrl())
        .build();
  }

  @Bean
  public HttpServiceProxyFactory httpServiceProxyFactory(
      WebClient webClient) {
    return HttpServiceProxyFactory
        .builder(WebClientAdapter.forClient(webClient))
        .build();
  }

  @Bean
  public MyRemoteServiceApi myGateway(HttpServiceProxyFactory httpServiceProxyFactory) {
    return httpServiceProxyFactory.create(MyRemoteServiceApi.class);
  }

  private static ObjectMapper createObjectMapper() {
    ObjectMapper mapper = new ObjectMapper();
    mapper.setPropertyNamingStrategy(PropertyNamingStrategies.KEBAB_CASE);
    mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
    mapper.setSerializationInclusion(Include.NON_NULL);
    mapper.registerModule(new JavaTimeModule());
    return mapper;
  }
}

I am using 2 customizations:

  1. A custom Jackson ObjectMapper because the application I am calling through the webclient uses kebab-case. The API exposed from my own application uses the normal pascalCase.
  2. Increase the maximum memory size the Jackson2JsonDecoder can use to read out responses.

How can I migrate this code to use the RestClient of Spring Boot 3.2?

For the custom object mapper, I now have done this:

@Bean
public RestClient restClient() {
    return RestClient.builder()
        .messageConverters(httpMessageConverters -> {
          httpMessageConverters.clear();
          MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
          converter.setObjectMapper(createObjectMapper());
          httpMessageConverters.add(converter);
        })
        .baseUrl(this.properties.baseUrl())
        .build();
  }

This seems to work, but I find it strange I have to first clear the list. Is this really the way it should be done?

Second question: how do I set that maxInMemorySize? Or is that not needed with RestClient?

Sushi answered 29/1 at 9:39 Comment(2)
Do you have any news on this? That's an interesting topic. Also, converters (Jackson) appear to be unmodifiableWadding
The Spring RestClient has a fluent API but uses blocking I/O. For truly high concurrent scenarios, consider Spring WebClient non-blocking approach for handling multiple requests simultaneously without waiting for each response.Effieeffigy
G
1

A cleaner alternative to walrus03's answer:

RestClient.Builder restClientBuilder = RestClient.builder();
restClientBuilder.messageConverters(c -> {
    // Remove any existing MappingJackson2HttpMessageConverter
    c.removeIf(MappingJackson2HttpMessageConverter.class::isInstance);
    // Add MappingJackson2HttpMessageConverter
    c.add(new MappingJackson2HttpMessageConverter(newObjectMapper));
});
Ghost answered 2/10 at 14:27 Comment(0)
O
3

This is how we have done it (It's a kotlin code):

RestClient.builder()
   .messageConverters { messageConverters ->
        val newConverters = messageConverters.map { converter ->
            if (converter is MappingJackson2HttpMessageConverter) {
                val newObjectMapper = createObjectMapper()
                MappingJackson2HttpMessageConverter(newObjectMapper)
            } else {
                converter
            }
        }
        messageConverters.clear()
        newConverters.forEach { messageConverters.add(it) }
    }

By doing like that, you can preserve other converters initialized by Spring by default, andupdate only MappingJackson2HttpMessageConverter ones.

Orifice answered 13/3 at 15:49 Comment(0)
G
1

A cleaner alternative to walrus03's answer:

RestClient.Builder restClientBuilder = RestClient.builder();
restClientBuilder.messageConverters(c -> {
    // Remove any existing MappingJackson2HttpMessageConverter
    c.removeIf(MappingJackson2HttpMessageConverter.class::isInstance);
    // Add MappingJackson2HttpMessageConverter
    c.add(new MappingJackson2HttpMessageConverter(newObjectMapper));
});
Ghost answered 2/10 at 14:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.