69

I'm using a spring-webflux WebClient (build 20170502.221452-172) to access a Web application producing a stream of Entry objects (application/stream+json) like this:

final WebClient producerClient = WebClient.create("http://localhost:8080/");

Flux<Entry> entries = producerClient.get().uri("json-stream")
        .accept(MediaType.APPLICATION_STREAM_JSON)
        .exchange()
        .flatMapMany(clientResponse -> clientResponse.bodyToFlux(Entry.class));

While the deserialization of the Entry objects works fine for POJOs using standard common types including Java time (JSR-310) datatypes like java.time.Instant, I wonder what I would have to do in order to add any custom JSON to Java deserialization (e. g., a custom Jackson ObjectMapper).

I can't find any API in WebClient or in the classes of the objects produced by its builder and fluent APIs to do that.

Has anybody used WebClient with customized deserialization?

(Maybe the API is not there, yet?)

Martin
  • 766
  • 1
  • 5
  • 8

9 Answers9

70

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();
Chris
  • 3,437
  • 6
  • 40
  • 73
Brian Clozel
  • 56,583
  • 15
  • 167
  • 176
  • 7
    Could you tell the reason why Jackson2ObjectMapperBuilderCustomizer is not applied to default codecs? – hahn Sep 20 '17 at 19:04
  • This sounds like a new question to me - could you create one? – Brian Clozel Sep 20 '17 at 19:12
  • 11
    @hahn If you use the preconfigured WebClient.Builder provided by spring instead of Webclient.builder(), you can get the objectMapper customizations done automatically Ref: https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-webclient – Sai Surya Kattamuri May 26 '20 at 01:47
13

Based on the replies above, I ended up with this code:

final ObjectMapper mapper = new ObjectMapper()
    .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.

Boris Azanov
  • 4,408
  • 1
  • 15
  • 28
SeverityOne
  • 2,476
  • 12
  • 25
10

You can configure this for a specific WebClient:

@Autowired
public ItunesAlbumServiceImpl(ObjectMapper mapper) {
    ExchangeStrategies strategies = ExchangeStrategies.builder().codecs(clientCodecConfigurer ->
        clientCodecConfigurer.customCodecs().decoder(
                new Jackson2JsonDecoder(mapper,
                        new MimeType("text", "javascript", StandardCharsets.UTF_8)))
    ).build();

    webClient = WebClient.builder()
            .exchangeStrategies(strategies)
            .baseUrl("https://itunes.apple.com")
            .build();
}

But also on 'application level'

by configuring a CodecCustomizer:

@Bean
public CodecCustomizer jacksonLegacyJsonCustomizer(ObjectMapper mapper) {
    return (configurer) -> {
        MimeType textJavascript = new MimeType("text", "javascript", StandardCharsets.UTF_8);
        CodecConfigurer.CustomCodecs customCodecs = configurer.customCodecs();
        customCodecs.decoder(
                new Jackson2JsonDecoder(mapper, textJavascript));
        customCodecs.encoder(
                new Jackson2JsonEncoder(mapper, textJavascript));
    };
}

which will be made effective by the WebClientAutoConfiguration as a WebClient.Builder bean:

@Autowired
public ItunesAlbumServiceImpl(WebClient.Builder webclientBuilder) {
    webClient = webclientBuilder.baseUrl("https://itunes.apple.com").build();
}
Ricardo
  • 101
  • 1
  • 2
8

Configuring globally:

@Configuration
public class AppConfig {

    private final ObjectMapper objectMapper;

    @Autowired
    public AppConfig(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
        this.webClientBuilder = WebClient.builder()
                .exchangeStrategies(exchangeStrategies());
    }

    private ExchangeStrategies exchangeStrategies() {
        Jackson2JsonEncoder encoder = new Jackson2JsonEncoder(objectMapper);
        Jackson2JsonDecoder decoder = new Jackson2JsonDecoder(objectMapper);
        return ExchangeStrategies
                .builder()
                .codecs(configurer -> {
                    configurer.defaultCodecs().jackson2JsonEncoder(encoder);
                    configurer.defaultCodecs().jackson2JsonDecoder(decoder);
                }).build();
    }
}
Sarvar Nishonboyev
  • 12,262
  • 10
  • 69
  • 70
6

Since Spring 5.1.13 you can use a dedicated .codec method to customize them:

WebClient.builder()
    .codecs(configurer -> {
        configurer.defaultCodecs().jackson2JsonEncoder(new Jackson2JsonEncoder(new ObjectMapper(), MediaType.APPLICATION_JSON));
        configurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(new ObjectMapper(), MediaType.APPLICATION_JSON));
     })
    .build();
tomasulo
  • 1,252
  • 8
  • 9
  • Thanks for this - I should really update the question with code for more recent Spring versions... – Martin May 18 '20 at 14:17
5

with webflux 5.0.2, de-registerDefaults

val strategies = ExchangeStrategies.builder()
                .codecs { configurer ->
                    configurer.registerDefaults(false)
                    configurer.customCodecs().encoder(Jackson2JsonEncoder(objectMapper, APPLICATION_JSON))
                    configurer.customCodecs().decoder(Jackson2JsonDecoder(objectMapper, APPLICATION_JSON))
                }.build()
Amit Yatagiri
  • 425
  • 1
  • 4
  • 16
1

If anyone interested in customizing the WebTestClient during a test, then it would be a similar approach. Just need to mutate the original bean.

@Autowired
private WebTestClient webTestClient;

@Test
void test() {
    webTestClient.mutate()
        .codecs(clientCodecConfigurer -> clientCodecConfigurer.defaultCodecs()
            .jackson2JsonDecoder(new Jackson2JsonDecoder(CUSTOM_OBJECT_MAPPER, MediaType.APPLICATION_JSON)))
        .build()
        .get()
        .uri("/test-endpoint")
        .exchange()
        .expectStatus().isOk();

ibai
  • 1,143
  • 1
  • 17
  • 28
0

In case you use Spring Boot then you can use auto-configured ObjectMapper and omit verbose codecs configuration. Just define bean like this:

@Bean
public WebClient webClient(WebClient.Builder builder) {
    return builder.build();
}

As I explained here Spring Boot does auto-configure WebClient builder. When you create instance by hand it uses built-in defaults and then you have to manually update codecs using one of methods above.

nouveu
  • 162
  • 4
  • 9
0

The most direct, concise, and surgical way I've found, which I read about in in Baeldung's Spring Boot: Customize the Jackson ObjectMapper, is to use a Jackson2ObjectMapperBuilderCustomizer. It looks like this:

@Configuration
public class MyAppConfiguration {

  @Bean
  public Jackson2ObjectMapperBuilderCustomizer jacksonJsonCustomizer() {
    return builder -> builder.modulesToInstall(/*TODO specify modules here);
  }

}

I've verified that this works with Spring Data as well as WebFlux, e.g. WebClient and .bodyToMono(SomeType.class). It even works in conjunction with WebClientCustomizer if you happen to be using that for customizing the WebClient; see the answers to Spring WebClient.Builder timeout defaults and overrides for runtimes .

Garret Wilson
  • 18,219
  • 30
  • 144
  • 272