Search code examples
javaspringspring-bootwebclient

Retry for Spring @HttpExchange


I'm using Spring-boot-3 @GetExchange with a WebClient, sometimes we hit a following error:

java.lang.IllegalStateException: Timeout on blocking read for 5000000000 NANOSECONDS

What's the best practice, in case of using @GetExchange to instruct the WebClient to retry? There isn't enough documentation for this new annotation.


Solution

  • You need to use ExchangeFilterFunctions that will help you define your retry logic.

        @Bean
        FooClient fooClient() {
            WebClient webClient =WebClient.builder()
                .baseUrl("your_base_url")
                .filter(withRetryableRequests())
                .build();
            HttpServiceProxyFactory factory=HttpServiceProxyFactory
                .builder(WebClientAdapter.forClient(webClient))
                .blockTimeout(Duration.of(HTTP_CLIENT_READ_TIMEOUT, ChronoUnit.SECONDS)) // Configure how long to wait for a response for an HTTP service method with a synchronous (blocking) method signature.By default this is 5 seconds.
                .build();
            return factory.createClient(FooClient.class);
        }
    
      
        private ExchangeFilterFunction withRetryableRequests() {
            return (request, next) -> next.exchange(request)
                    .flatMap(clientResponse -> Mono.just(clientResponse)
                            .filter(response ->  clientResponse.statusCode().isError())
                            .flatMap(response -> clientResponse.createException())
                            .flatMap(Mono::error)
                            .thenReturn(clientResponse))
                    .retryWhen(this.retryBackoffSpec());
        }
        
        private RetryBackoffSpec retryBackoffSpec() {
            return Retry.backoff(3, Duration.ofSeconds(2))
                    .filter(throwable->throwable instanceof  WebClientResponseException) // here filter on the errors for which you want a retry
                    .doBeforeRetry(retrySignal -> log.warn("Retrying request after following exception : {}",  retrySignal.failure().getLocalizedMessage()))
                    .onRetryExhaustedThrow((retryBackoffSpec, retrySignal)  -> retrySignal.failure());
        }
    

    [EDIT] For the particular case that you encounter, i.e. the exception

    java.lang.IllegalStateException: Timeout on blocking read for 5000000000 NANOSECONDS
    

    the probable cause is that your service is waiting for the answer to the request and after 5 seconds (by default), the service hangs up.

    It would be necessary to increase the read timeout of the http client used. I have edited the code accordingly with a comment on the line concerned.