6

I have the following spring mvc method that returns a file:

@RequestMapping(value = "/files/{fileName}", method = RequestMethod.GET)
public FileSystemResource getFiles(@PathVariable String fileName){

    String path="/home/marios/Desktop/";

    return new FileSystemResource(path+fileName);

}

I expect a ResourceHttpMessageConverter to create the appropriate response with an octet-stream type according to its documentation:

If JAF is not available, application/octet-stream is used.

However although I correctly get the file without a problem, the result has Content-Type: application/json;charset=UTF-8

Can you tell me why this happens?

(I use spring version 4.1.4. I have not set explicitly any message converters and I know that spring loads by default among others the ResourceHttpMessageConverter and also the MappingJackson2HttpMessageConverter because I have jackson 2 in my classpath due to the fact that I have other mvc methods that return json.

Also if I use HttpEntity<FileSystemResource> and set manually the content type, or specify it with produces = MediaType.APPLICATION_OCTET_STREAM it works fine.

Note also that in my request I do not specify any accept content types, and prefer not to rely on my clients to do that)

Marios
  • 1,947
  • 1
  • 15
  • 24
  • 1
    I think you've answered your own question, it sounds like the jackson converter is taking precedence over the resource converter. – Jaimie Whiteside Jan 28 '15 at 16:24
  • The JAF is always available except if you use a very old Java version ;) – a better oliver Jan 28 '15 at 18:04
  • But the result is written successfully by ResourceHttpMessageConverter because it is a file , why is the content type taken from MappingJackson2HttpMessageConverter? – Marios Jan 29 '15 at 09:33
  • see my answer here http://stackoverflow.com/questions/34182523/spring-rest-web-service-return-file-as-resource – Wilder Valera Sep 15 '16 at 14:32

2 Answers2

5

I ended up debugging the whole thing, and I found that AbstractJackson2HttpMessageConverter has a canWrite implementation that returns true in case of the FileSystemResource because it just checks if class is serializable, and the set media type which is null since I do not specify any which in that case is supposed to be supported by it.

As a result it ends up putting the json content types in a list of producible media types. Of course ResourceHttpMessageConverter.canWrite implementation also naturally returns true, but the ResourceHttpMessageConverter does not return any producible media types.

When the time to write the actual response comes, from the write method implementation, the write of the ResourceHttpMessageConverter runs first due to the fact that the ResourceHttpMessageConverter is first in the list of the available converters (if MappingJackson2HttpMessageConverter was first, it would try to call write since its canWrite returns true and throw exception), and since there was already a producible content type set, it does not default to running the ResourceHttpMessageConverter.getDefaultContentType that would set the correct content type.

If I remove json converter all would work fine, but unfortunately none of my json methods would work. Therefore specifying the content type is the only way to get rid of the returned json content type

herau
  • 1,466
  • 2
  • 18
  • 36
Marios
  • 1,947
  • 1
  • 15
  • 24
2

For anyone still looking for a piece of code:

You should wrap your FileSystemResource into a ResponseEntity<> Then determine your image's content type and append it to ResponseEntity as a header.

Here is an example:

@GetMapping("/image")
public @ResponseBody ResponseEntity<FileSystemResource> getImage() throws IOException {

    File file = /* load your image file from anywhere */;
    if (!file.exists()) {
        //TODO: throw 404
    }

    FileSystemResource resource = new FileSystemResource(file);
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(/* determine your image's media type or just set it as a constant using MediaType.<value> */);
    headers.setContentLength(resource.contentLength());

    return new ResponseEntity<>(resource, headers, HttpStatus.OK);
}
alturkovic
  • 990
  • 8
  • 31
H4kt
  • 133
  • 7