springspring-bootspring-webflux

Migrate from RestTemplate to WebFlux


I have this RestTemplate which I want to migrate to WebFlux client:

RestTemplate restTemplate = new RestTemplate();
String url = "https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml";
ResponseEntity<Envelope> response = restTemplate.getForEntity(url, Envelope.class);

I tried this:

Boolean webClient = WebClient.builder()
                .build()
                .get()
                .uri("https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml")
                .accept(MediaType.APPLICATION_XML)
                .exchangeToMono(res -> Mono.just(res.statusCode().equals(HttpStatus.OK)))
                .timeout(Duration.ofSeconds(2))
                .block();

I'm not clear how I can get the response and read the response as JAVA DTO. Do you know how I have to implement his code?


Solution

  • To make a request and retrieve a response you need to call the retrieve function on the WebClient. Then you can parse the response Body with bodyToMono. It's important to know that Mono is a single Object and Flux are Collection-Types

    Envelope envelope = WebClient.builder()
                    .build()
                    .get()
                    .uri("https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml")
                    .accept(MediaType.APPLICATION_XML)
                    .exchangeToMono(res -> Mono.just(res.statusCode().equals(HttpStatus.OK)))
                    .timeout(Duration.ofSeconds(2))
                    .retrieve()
                    .bodyToMono(Envelope.class)
                    .block();
    

    There's also a nice example and explanation on this page: https://www.baeldung.com/webflux-webclient-parameters

    But I would suggest you to structure your code a little bit more. For example, as you're using Spring Boot think about using that WebClientBuilder to create a WebClient as Bean, that WebClient would be configured to have a base url (https://www.ecb.europa.eu). Then you have a service where you inject that WebClient and make a call to receive your Envelope. Something like this would enable you to use the same WebCLient for different calls to the same host:

    import java.util.List;
    import java.util.concurrent.TimeUnit;
    
    import io.netty.channel.ChannelOption;
    import io.netty.handler.timeout.ReadTimeoutHandler;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.http.MediaType;
    import org.springframework.http.client.reactive.ClientHttpConnector;
    import org.springframework.http.client.reactive.ReactorClientHttpConnector;
    import org.springframework.stereotype.Service;
    import org.springframework.web.reactive.function.client.WebClient;
    import reactor.netty.http.client.HttpClient;
    
    @Configuration
    public class RestConfig {
        @Bean
        public WebClient ecbEuropaWebClient(WebClient.Builder webClientBuilder) {
            HttpClient httpClient = HttpClient.create().tcpConfiguration(
                    (client) -> client.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 2000).doOnConnected(
                            (connection) -> connection.addHandlerLast(
                                    new ReadTimeoutHandler(2000, TimeUnit.MILLISECONDS))));
            ClientHttpConnector connector = new ReactorClientHttpConnector(httpClient);
            return webClientBuilder
                    .baseUrl("https://www.ecb.europa.eu")
                    .clientConnector(connector)
                    .build();
        }
    }
    
    @Service
    public class EcbEuropaService {
        @Autowired
        private WebClient ecbEuropaWebClient;
    
        public Envelope getEnvelope() {
            return ecbEuropaWebClient.get()
                    .uri(uriBuilder -> uriBuilder.path("/stats/eurofxref/eurofxref-daily.xml").build())
                    .accept(MediaType.APPLICATION_XML).retrieve().bodyToMono(Envelope.class).block();
        }
    }