Here's an example that customizes the ObjectMapper for JSON (de)serialization.
Note that for streaming purposes, different encoders/decoders are being used but the principle remains the same for their configuration.
ExchangeStrategies strategies = ExchangeStrategies
.builder()
.codecs(clientDefaultCodecsConfigurer -> {
clientDefaultCodecsConfigurer.defaultCodecs().jackson2JsonEncoder(new Jackson2JsonEncoder(new ObjectMapper(), MediaType.APPLICATION_JSON));
clientDefaultCodecsConfigurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(new ObjectMapper(), MediaType.APPLICATION_JSON));
}).build();
WebClient webClient = WebClient.builder().exchangeStrategies(strategies).build();
Answer from Brian Clozel on Stack OverflowHere's an example that customizes the ObjectMapper for JSON (de)serialization.
Note that for streaming purposes, different encoders/decoders are being used but the principle remains the same for their configuration.
ExchangeStrategies strategies = ExchangeStrategies
.builder()
.codecs(clientDefaultCodecsConfigurer -> {
clientDefaultCodecsConfigurer.defaultCodecs().jackson2JsonEncoder(new Jackson2JsonEncoder(new ObjectMapper(), MediaType.APPLICATION_JSON));
clientDefaultCodecsConfigurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(new ObjectMapper(), MediaType.APPLICATION_JSON));
}).build();
WebClient webClient = WebClient.builder().exchangeStrategies(strategies).build();
Based on the replies above, I ended up with this code:
final ObjectMapper mapper = Jackson2ObjectMapperBuilder
.json()
.build()
.findAndRegisterModules()
.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
final ExchangeStrategies exchangeStrategies = ExchangeStrategies.builder()
.codecs(configurer -> configurer.defaultCodecs()
.jackson2JsonDecoder(new Jackson2JsonDecoder(mapper)))
.build();
final WebClient webClient = WebClient.builder()
.exchangeStrategies(exchangeStrategies)
.build();
If you don't include .findAndRegisterModules(), you're going to have problems when you want to deserialise things like Java 8's time objects.
Edit: changed the way that the ObjectMapper is created, based on our own experiences.
java - Custom ObjectMapper with Spring RestClient (migrating from WebClient) - Stack Overflow
WebClient fails to send POST body when ObjectMapper FLUSH_AFTER_WRITE_VALUE is false
WebClientConfigurer "drops" ObjectMapper customizations from WebClients
How to map WebClient response to a custom DTO with mismatched fields in Spring Boot?
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));
});
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.
I am working on a Spring Boot project and using WebClient to fetch data from an external API (TMDB). I want to map the JSON response from WebClient to my custom DTO class, but the fields in the JSON response don't exactly match the fields in my DTO.
This is my dto
public class GetMovieRequest {
private Long tmdbId;
private String title;
private List<String> genres;
private String overview;
private LocalDate releaseDate;
private Integer runtime;}
but TMDB json has a lot of fields that I don't need to use so I can't exactly use the direct mapping bodyToMono(GetMovieRequest.class) so I use JsonNode instead:
JsonNode node = webClient.get()
.uri([some uri])
.header("Authorization", "Bearer ...")
.retrieve()
.bodyToMono(JsonNode.class)
.block();but I don't think this code is optimized, is there another way?
Thanks in advanced
After quite some time spent debugging Spring WebFlux / Jackson library code, I was able to finally find a hint to solve the issue looking at the reactive WebFlux WebClient docs. There is some custom plumbing required to make WebClient use the default auto-configured Jackson ObjectMapper. The solution is to configure the default decoder used for processing Server-Sent Events when creating new instance of the WebClient. Here is the sample code:
@Component
public class MarketDataFetcher implements CommandLineRunner {
// ...
private final WebClient webClient;
public MarketDataFetcher(ObjectMapper objectMapper) {
webClient = createWebClient(objectMapper);
}
private WebClient createWebClient(ObjectMapper objectMapper) {
return WebClient.builder()
.codecs(configurer -> configurer.defaultCodecs()
.serverSentEventDecoder(new Jackson2JsonDecoder(objectMapper)))
.baseUrl(BASE_URL)
.build();
}
}
ObjectMapper is automatically injected by Spring, so no @Autowired annotation is needed.
It would certainly help if this could be made more explicit in the official documentation somehow. Hope this answer can be useful for someone facing similar issue!
I found in Spring Boot docs that:
the client instance is built using the WebClient.Builder bean auto-configured by Spring Boot
There is also section dedicated to WebClient customization which says:
To make the scope of any customizations as narrow as possible, inject the auto-configured WebClient.Builder and then call its methods as required.
Therefore simplest way to use configured ObjectMapper is to define WebClient bean like this:
@Bean
public WebClient webClient(WebClient.Builder builder) {
return builder.build();
}
That is pretty unconvenient that Spring Boot doesn't do that by default given how any other stuff is already configured to use out of box but it is much better than configuring codecs by hand.