Search code examples
javaspring-bootspring-webclient

org.springframework.web.reactive.function.UnsupportedMediaTypeException: Content type 'text/html;charset=iso-8859-1' not supported for bodyType=


Using Java 11, Springt Boot WebClient

Created a REST Webservice(Service 1) which tries to consume a REST WebService(Service 2) which is expected to return JSON which would be mapped to my custom response object. But because the service(Service 2) to consume is not online at the moment I will receive 404 not found.

Problem seems to be that 404 response comes in form of 'text/html;charset=iso-8859-1' and cannot be mapped on my DocumentResponse Object.

Webclient is configured to accept xml and json.

How can I intercept and handle those http status responses without getting this exception.

Normally I would receive the Response by using.block() on my Request chain and create a proper Response for service 1 which is consuming this one.

ex:

Documentresponse response = invoiceDeliveryControllerApi.deliverInvoiceUsingPOST(request,"test").block(); 
public Mono<Documentresponse> deliverInvoiceUsingPOST(Documentrequest request, String name) throws WebClientResponseException {
        Object postBody = request;
        // verify the required parameter 'request' is set
        if (request == null) {
            throw new WebClientResponseException("Missing the required parameter 'request' when calling deliverInvoiceUsingPOST", HttpStatus.BAD_REQUEST.value(), HttpStatus.BAD_REQUEST.getReasonPhrase(), null, null, null);
        }
        // create path and map variables
        final Map<String, Object> pathParams = new HashMap<String, Object>();

        final MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<String, String>();
        final HttpHeaders headerParams = new HttpHeaders();
        final MultiValueMap<String, String> cookieParams = new LinkedMultiValueMap<String, String>();
        final MultiValueMap<String, Object> formParams = new LinkedMultiValueMap<String, Object>();

        queryParams.putAll(apiClient.parameterToMultiValueMap(null, "name", name));

        final String[] localVarAccepts = { 
            "application/xml", "text/xml"
        };
        final List<MediaType> localVarAccept = apiClient.selectHeaderAccept(localVarAccepts);
        final String[] localVarContentTypes = { 
            "application/xml", "text/xml"
        };
        final MediaType localVarContentType = apiClient.selectHeaderContentType(localVarContentTypes);

        String[] localVarAuthNames = new String[] {  };

        ParameterizedTypeReference<Documentresponse> localVarReturnType = new ParameterizedTypeReference<Documentresponse>() {};
        return apiClient.invokeAPI("/api/v1/deliverinvoice", HttpMethod.POST, pathParams, queryParams, postBody, headerParams, cookieParams, formParams, localVarAccept, localVarContentType, localVarAuthNames, localVarReturnType);
    }
 public <T> Mono<T> invokeAPI(String path, HttpMethod method, Map<String, Object> pathParams, MultiValueMap<String, String> queryParams, Object body, HttpHeaders headerParams, MultiValueMap<String, String> cookieParams, MultiValueMap<String, Object> formParams, List<MediaType> accept, MediaType contentType, String[] authNames, ParameterizedTypeReference<T> returnType) throws RestClientException {
        final WebClient.RequestBodySpec requestBuilder = prepareRequest(path, method, pathParams, queryParams, body, headerParams, cookieParams, formParams, accept, contentType, authNames);
        return requestBuilder.retrieve().bodyToMono(returnType);
    }
   public static WebClient buildWebClient(ObjectMapper mapper) {
        ExchangeStrategies strategies = ExchangeStrategies
            .builder()
            .codecs(clientDefaultCodecsConfigurer -> {
                clientDefaultCodecsConfigurer.defaultCodecs().jackson2JsonEncoder(new Jackson2JsonEncoder(mapper, MediaType.APPLICATION_JSON));
                clientDefaultCodecsConfigurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(mapper, MediaType.APPLICATION_JSON));
            }).build();
        WebClient.Builder webClient = WebClient.builder().filters(exchangeFilterFunctions -> {
            exchangeFilterFunctions.add(logRequest());
            exchangeFilterFunctions.add(logResponse());
        }).exchangeStrategies(strategies);
        return webClient.build();
    }

Now I would like to find a way to react to those html responses and create a response for service 1 which would tell the user that there was an http connection error while calling Service 2

Any suggestions?

PS: Client Code was generated with openapi code generator and slightly adjusted for logging purposes.

EDIT: I discovered that normally it is as easy as catching WebClientException at .block() and with the status code I can generate a satisfying response message.

But a strange behavior I discovered is that if the response of Service 2 is f.e. 400 Bad Request it is delivered as text/html without charset=iso-8859-1 addition.

In this case it leads to an expected WebClientResponseException. But for the instance of 404 not found text/html charset=iso-8859-1 is delivered and leads to the UnexpectedMediaTypeException. Is there a way to explain why?

Even More Confusing to me is that in the case of expected 404 my logging via WebClientFilters delivers status 307 redirect(to the same url i am calling in the first place) and 302 found with text/html charset=iso-8859-1 also leading to the MediaTypeException. While the same call with exact same data via Postman deliver 404. Anyone got a clue what is happening?


Solution

  • From WebBlient#bodyToMono java doc:

    Extract the body to a {@code Mono}. By default, if the response has status code 4xx or 5xx, the {@code Mono} will contain a {@link WebClientException}. This can be overridden with {@link #onStatus(Predicate, Function)}.

    To achive error handling depending on status codes:

    requestBuilder
                .retrieve()
                .onStatus(HttpStatus::is4xxClientError, res -> Mono<? extends Throwable>)
                .onStatus(HttpStatus::is5xxServerError, res -> Mono<? extends Throwable>)
                .bodyToMono(responseType)
                .onErrorResume(Status400Throwable.class, th -> Mono.just(fallbackResponseHere)) 
                .onErrorResume(Status500Throwable.class, th -> Mono.just(fallbackResponseHere));
    

    Have a look at WebClient.ResponseSpec#onStatus java doc as well as at something like Mono#onErrorResume in case you do not want the default error handler to return WebClientResponseException.