1

I have a Spring Boot Rest End Point defined in an interface to download an image

@GetMapping(value = "/{name}")
ResponseEntity<ByteArrayResource> getFileByName(@PathVariable("name") String name);

And I use Feign Builder to invoke this end point.

Feign.builder()
    .client(new ApacheHttpClient())
    .contract(new SpringMvcContract())
    .decoder(new JacksonDecoder())
    .encoder(new JacksonEncoder())  
    .target(clazz, url)

On invoking, I get below error

com.fasterxml.jackson.core.JsonParseException: Unexpected character ('�' (code 65533 / 0xfffd)): expected a valid value (number, String, array, object, 'true', 'false' or 'null')
 at [Source: (BufferedReader); line: 1, column: 2]

When I try to invoke the end point directly from Insomnia, it works fine. But fails through Feign Builder. The response content type is image/jpeg

Is there any specific decoder in feign to handle ByteArrayResource? I tried ResponseEntityDecoder, StreamDecoder and JacksonDecoder. None of it works.

On debugging, I see that Jackson ObjectMapper readValue fails. I tried changing the return type from ByteArraySource to byte[], didn't work either.

Any help?

Daniel Widdis
  • 8,424
  • 13
  • 41
  • 63
Maz
  • 653
  • 12
  • 22
  • Please look at this question: https://stackoverflow.com/questions/30660655/org-codehaus-jackson-jsonparseexception-unexpected-character-code-65533 – Dhruv Patel Sep 24 '19 at 02:39
  • Already came across that link and I didn't find any solution in it for feign – Maz Sep 24 '19 at 02:53
  • It looks like you need to use another decoder for file response. Probably `Jackson decoder` treats response as a `JSON` and this why it throws exception. Take a look on documentation [Spring MultipartFile and Spring Cloud Netflix @FeignClient support](https://github.com/OpenFeign/feign-form#spring-multipartfile-and-spring-cloud-netflix-feignclient-support) and [How to download File with open-feign](https://stackoverflow.com/questions/52744112/how-to-download-file-with-open-feign) – Michał Ziober Sep 24 '19 at 17:57

2 Answers2

3

I wrote my own little decoder and the problem was resolved. Below is the decoder

private Decoder byteArrayResourceDecoder() {
        Decoder decoder = (response, type) -> {
            if (type instanceof Class && ByteArrayResource.class.isAssignableFrom((Class) type)) {
                return StreamUtils.copyToByteArray(response.body().asInputStream());
            }
            return new JacksonDecoder().decode(response, type);
        };

        return new ResponseEntityDecoder(decoder);
    }

Hope this template helps others who has similar issues. Would have expected Feign to have decoder that supports all return types.

Maz
  • 653
  • 12
  • 22
1

Thanks Maz - your solution helped me.

I modified your solution for my needs to read Spring StreamingResponseBody

1.) Create the decoder wrapper that either returns JacksonDecoder (Default) or reads the responsebody into a byte array.

Decoder decoder = (response, type) -> {
                
                Map<String, Collection<String>> headers = response.headers();
                Collection<String> contentType = null;
                for (String x : headers.keySet()){
                    if ("content-type".equals(x.toLowerCase())){
                        contentType = headers.get(x);                      
                    } 
                }

                if (contentType == null || contentType.stream().filter(x -> x.contains("application/json")).findFirst().isPresent()) {                    
                    return new JacksonDecoder(getMapper()).decode(response, type);
                }

                InputStream initialStream = response.body().asInputStream();
                byte[] buffer = new byte[512];
                byte[] result = null;

                try(ByteArrayOutputStream out = new ByteArrayOutputStream()) {
                    try {
                        int length = 0;
                        while ((length = initialStream.read(buffer)) != -1) {
                            out.write(buffer, 0, length);
                        }
                    } finally {
                        out.flush();
                    }
                    result = out.toByteArray();
                } finally {
                    initialStream.close();
                }

                return result;
            };

2.) Use the custom decoder with the Feign.Builder

Feign.Builder builder = Feign.builder()
                    // --
                    .decoder(decoder)
                    // --