17

I've tried the various ways given in Stackoverflow, maybe I missed something.

I have an Android client (whose code I can't change) which is currently getting an image like this:

HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.connect();

Where url is the location of the image (static resource on CDN). Now my Spring Boot API endpoint needs to behave like a file resource in the same way so that the same code can get images from the API (Spring boot version 1.3.3).

So I have this:

@ResponseBody
@RequestMapping(value = "/Image/{id:.+}", method = RequestMethod.GET, consumes = MediaType.ALL_VALUE, produces = MediaType.IMAGE_JPEG_VALUE)
public ResponseEntity<byte[]> getImage(@PathVariable("id")String id) {
    byte[] image = imageService.getImage(id);  //this just gets the data from a database
    return ResponseEntity.ok(image);
}

Now when the Android code tries to get http://someurl/image1.jpg I get this error in my logs:

Resolving exception from handler [public org.springframework.http.ResponseEntity com.myproject.MyController.getImage(java.lang.String)]: org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation

Same error happens when I plug http://someurl/image1.jpg into a browser.

Oddly enough my tests check out ok:

Response response = given()
            .pathParam("id", "image1.jpg")
            .when()
            .get("MyController/Image/{id}");

assertEquals(HttpStatus.OK.value(), response.getStatusCode());
byte[] array = response.asByteArray(); //byte array is identical to test image

How do I get this to behave like an image being served up in the normal way? (Note I can't change the content-type header that the android code is sending)

EDIT

Code after comments (set content type, take out produces):

@RequestMapping(value = "/Image/{id:.+}", method = RequestMethod.GET, consumes = MediaType.ALL_VALUE)
public ResponseEntity<byte[]> getImage(@PathVariable("id")String id, HttpServletResponse response) {
    byte[] image = imageService.getImage(id);  //this just gets the data from a database
    response.setContentType(MediaType.IMAGE_JPEG_VALUE);
    return ResponseEntity.ok(image);
}

In a browser this just seems to give a stringified junk (byte to chars i guess). In Android it doesn't error, but the image doesn't show.

Alykoff Gali
  • 1,121
  • 17
  • 32
Manish Patel
  • 4,411
  • 4
  • 25
  • 48
  • What does the client send as its Accept header? What happens when you remove the produces attribute, and just specify the content type in the response? – JB Nizet Nov 11 '16 at 23:30
  • @JBNizet the Accept header is whatever the default is for HttpURLConnection (Android SDK). It's definitely not specifically set. I tried omitting `produces` but that seems to deliver plain text (in a browser). Setting content-type is same as `produces` anyway, no? – Manish Patel Nov 12 '16 at 20:45
  • @JBNizet I tried setting the content type explicitly too, I get the same error in the log – Manish Patel Nov 12 '16 at 21:15
  • https://youtu.be/Jv7TLWjOz4g – Manjitha Teshara Apr 28 '20 at 17:25
  • this well prepared video tutorial step by step guide, to how to read image from spring boot backend, in hear front end working on Angular. – Manjitha Teshara Apr 28 '20 at 17:28

4 Answers4

24

I believe this should work:

@RequestMapping(value = "/Image/{id:.+}", method = RequestMethod.GET)
public ResponseEntity<byte[]> getImage(@PathVariable("id") String id) {
    byte[] image = imageService.getImage(id);
    return ResponseEntity.ok().contentType(MediaType.IMAGE_JPEG).body(image);
}

Notice that the content-type is set for ResponseEntity, not for HttpServletResponse directly.

Roman
  • 6,486
  • 2
  • 23
  • 41
  • thanks, i did actually try this permutation but it did not work for me – Manish Patel Nov 21 '16 at 09:03
  • @user2393012 Strange, because I checked and it works for me. Did you notice I also removed `consumes`, `produces` and `@ResponseBody`? And what error did you get with this code? – Roman Nov 21 '16 at 11:59
  • 1
    yes, noticed the omissions. I didn't get any error actually, in the browser it just appeared as a empty frame – Manish Patel Nov 21 '16 at 12:28
  • @user2393012 Did you check the response headers? Empty frame could be an empty image. – Roman Nov 21 '16 at 12:33
  • no but the image from the database hasn't been touched - on Spring side i do output the `byte[]` length and it's fairly sizeable – Manish Patel Nov 21 '16 at 12:39
  • @user2393012 There is also a possibility that the request was handled by some other controller method. – Roman Nov 21 '16 at 12:45
12

Finally fixed this... I had to add a ByteArrayHttpMessageConverter to my WebMvcConfigurerAdapter subclass:

@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    final ByteArrayHttpMessageConverter arrayHttpMessageConverter = new ByteArrayHttpMessageConverter();
    final List<MediaType> list = new ArrayList<>();
    list.add(MediaType.IMAGE_JPEG);
    list.add(MediaType.APPLICATION_OCTET_STREAM);
    arrayHttpMessageConverter.setSupportedMediaTypes(list);
    converters.add(arrayHttpMessageConverter);

    super.configureMessageConverters(converters);
}
Manish Patel
  • 4,411
  • 4
  • 25
  • 48
8

In case you don't know the file/mime type you can do this.... I've done this where i take an uploaded file and replace the file name with a guid and no extension and browsers / smart phones are able to load the image no issues. the second is to serve a file to be downloaded.

@RestController
@RequestMapping("img")
public class ImageController {

@GetMapping("showme")
public ResponseEntity<byte[]> getImage() throws IOException{
    File img = new File("src/main/resources/static/test.jpg");
    return ResponseEntity.ok().contentType(MediaType.valueOf(FileTypeMap.getDefaultFileTypeMap().getContentType(img))).body(Files.readAllBytes(img.toPath()));
}
@GetMapping("thing")
public ResponseEntity<byte[]> what() throws IOException{
    File file = new File("src/main/resources/static/thing.pdf");
    return ResponseEntity.ok()
            .header("Content-Disposition", "attachment; filename=" +file.getName())
            .contentType(MediaType.valueOf(FileTypeMap.getDefaultFileTypeMap().getContentType(file)))
            .body(Files.readAllBytes(file.toPath()));
}


}   

UPDATE in java 9+ you need to add compile 'com.sun.activation:javax.activation:1.2.0' to your dependencies this has also been moved or picked up by jakarta.see this post

mavriksc
  • 1,130
  • 1
  • 7
  • 10
  • For some files (apple pass/wallet ) to work properly you have to include content length. – mavriksc Jun 29 '17 at 14:41
  • Thank you, `getImage()` method worked for me. But I had a `java.lang.ClassNotFoundException: com.sun.activation.registries.LogSupport` while setting `contentType()`. Finally I just used `contentType(MediaType.IMAGE_JPEG)` instead of dynamic content type creation and exception is gone. My way works not only for JPEG, but also for PNG. – Yamashiro Rion Mar 25 '19 at 12:30
  • i think this is an issue with java 9+. i may take a look at how to keep it dynamic and post an update. – mavriksc Mar 26 '19 at 17:20
  • Yes, you are right, I use Java 10. I hope you'll find a way. :) – Yamashiro Rion Mar 27 '19 at 06:31
  • 2
    Thank you, I have checked this solution and it really works! – Yamashiro Rion Mar 28 '19 at 05:15
0

Using Apache Commons, you can do this and expose the image on an endpoint

@RequestMapping(value = "/image/{imageid}",method= RequestMethod.GET,produces = MediaType.IMAGE_JPEG_VALUE)
public @ResponseBody byte[] getImageWithMediaType(@PathVariable int imageid) throws IOException {
    InputStream in = new ByteArrayInputStream(getImage(imageid));
    return IOUtils.toByteArray(in);
    }

All images will be served at endpoint /image/{imageid}

devyJava
  • 135
  • 8