Search code examples
spring-cloud-feignfeign

How to get rid of stream is closed error with FeignErrorDecoder?


I have a feign client service built like this :

Feign.Builder builder = Feign.builder()
        .contract(new SpringMvcContract())
        .encoder(new JacksonEncoder())
        .decoder(new JacksonDecoder())
        .errorDecoder(new FeignClientErrorHandler())
return builder.target(targetClass, url);

I have the FeignClientErrorDecoder which looks like this :

public class FeignClientErrorDecoder implements ErrorDecoder {

    private static final ObjectMapper MAPPER = new ObjectMapper().registerModule(new JavaTimeModule());

    @Override
    public Exception decode(final String methodKey,
                            final Response response) {
        try {
            byte[] body = Util.toByteArray(response.body().asInputStream());
            ApiError apiError = MAPPER.readValue(body, ApiError.class);
            return ExceptionFactory.createFrom(apiError);
        } catch (IOException | ApiErrorException e) {
            return new TechnicalClientException("Could not extract error payload.", e);
        }
    }

}

No matter which reading input stream solution I choose, I always get a stream is closed error. What am I missing ? Who is closing it ? Any workaround ?

Complete code here : https://github.com/louisamoros/feign-error-code You can run mvn clean install and see that 1 test is in error.


Solution

  • Thanks for the full code. The error occurs due to logging of response in error decoder:

    LOGGER.error("Feign client error handler. Method: {}, Response: {}", methodKey, response);
    

    Here, toString() is called on the response, including its body. So, input stream of the response body is read and closed there and cannot be read again later.

    You can either remove response from logging or copy its input stream (via apache IOUtils or smth similar) and then work with its duplicate. In that case, the mapper will parse everything successfully and the next line return new ApiException() will be reached.

    By the way, be careful with debugging this kind of code. Modern IDEs (e.g. IntelliJ IDEA) can invoke toString() on all objects in the scope while reaching a breakpoint by default, so the same problem can occur because of it too. Here, you can safely put the breakpoint only after MAPPER.readValue line.