download a file from Spring boot rest service
Asked Answered
S

7

155

I am trying to download a file from a Spring boot rest service.

@RequestMapping(path="/downloadFile",method=RequestMethod.GET)
    @Consumes(MediaType.APPLICATION_JSON_VALUE)
    public  ResponseEntity<InputStreamReader> downloadDocument(
                String acquistionId,
                String fileType,
                Integer expressVfId) throws IOException {
        File file2Upload = new File("C:\\Users\\admin\\Desktop\\bkp\\1.rtf");
        HttpHeaders headers = new HttpHeaders();
        headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
        headers.add("Pragma", "no-cache");
        headers.add("Expires", "0");
        InputStreamReader i = new InputStreamReader(new FileInputStream(file2Upload));
        System.out.println("The length of the file is : "+file2Upload.length());

        return ResponseEntity.ok().headers(headers).contentLength(file2Upload.length())
                .contentType(MediaType.parseMediaType("application/octet-stream"))
                .body(i);
        }

When I tried to download the file from the browser, it starts the download, but always fails. Is there anything wrong with the service which is causing the download to fail?

Seeress answered 28/2, 2016 at 9:42 Comment(0)
C
262

Option 1 using an InputStreamResource

Resource implementation for a given InputStream.

Should only be used if no other specific Resource implementation is > applicable. In particular, prefer ByteArrayResource or any of the file-based Resource implementations where possible.

@RequestMapping(path = "/download", method = RequestMethod.GET)
public ResponseEntity<Resource> download(String param) throws IOException {

    // ...

    InputStreamResource resource = new InputStreamResource(new FileInputStream(file));

    return ResponseEntity.ok()
            .headers(headers)
            .contentLength(file.length())
            .contentType(MediaType.APPLICATION_OCTET_STREAM)
            .body(resource);
}

Option2 as the documentation of the InputStreamResource suggests - using a ByteArrayResource:

@RequestMapping(path = "/download", method = RequestMethod.GET)
public ResponseEntity<Resource> download(String param) throws IOException {

    // ...

    Path path = Paths.get(file.getAbsolutePath());
    ByteArrayResource resource = new ByteArrayResource(Files.readAllBytes(path));

    return ResponseEntity.ok()
            .headers(headers)
            .contentLength(file.length())
            .contentType(MediaType.APPLICATION_OCTET_STREAM)
            .body(resource);
}
Celestinacelestine answered 28/2, 2016 at 13:42 Comment(12)
I am trying to do it for word document .doc format, but while downloading the format is gone and file is downloaded without file extension and the file name is response while downloading. Any suggestion?Inverness
@TulsiJain add the Content-Disposition HttpHeader: HttpHeaders headers = new HttpHeaders(); headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=myDoc.docx");Celestinacelestine
just in case you're unlucky enough to be using plain Spring instead of Spring Boot, you need to make sure that an instance of ResourceHttpMessageConverter is added to your list of HttpMessageConverters. Create a @Configuration class that extends WebMvcConfigurerAdapter, implement the configureMessageConverters() method and add a converters.add(new ResourceHttpMessageConverter()); lineJola
Questions: Option 1 does not seem to close the stream. Where is the magic? Option 2 seems to load the complete file into memory before sending. Correct? Alternatives? THX!Pitchfork
@Pitchfork In Option 1, Spring Boot will close the stream. See this: https://mcmap.net/q/152464/-how-to-handle-io-streams-in-spring-mvcHypogeum
Is there anyway to notify the client of an error in the middle of the streaming/zipping process ? It seems entering in the catch block does NOT effect the response to indicate anything other than closing the resources since the response already committed !Promise
For huge files ByteArrayResource will work? It will not take entire heap space? Or should we go for StreamingResponseBody so that Out Of Memory will not occur?Allman
hello @Celestinacelestine , i used httpHeaders.add(CONTENT_DISPOSITION, "attachment;File-Name="+filename); and when returning i used ResponseEntity.ok().contentType(MediaType.parseMediaType(Files.probeContentType(filePath))).headers(httpHeaders).body(resource) but it still throwing Invalid mime type "null": 'mimeType' must not be emptyEvaporate
shouldn't it be InputStream inputStream = resource.getInputStream(); for option 1? Original option 1 wont work in executable jar.?Moshe
I have a question; How can I download more then one file? I mean, I have 10 pdf files and I want to download all of them in only one request? Is there a way?Organelle
how do you escape the name of the file? in my case spaces and ( ) causes errorsTotem
@AlfredoMorales org.apache.commons.io.FilenameUtils.getName(file.getPath());Kira
M
66

The below Sample code worked for me and might help someone.

import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

@RestController
@RequestMapping("/app")
public class ImageResource {

    private static final String EXTENSION = ".jpg";
    private static final String SERVER_LOCATION = "/server/images";

    @RequestMapping(path = "/download", method = RequestMethod.GET)
    public ResponseEntity<Resource> download(@RequestParam("image") String image) throws IOException {
        File file = new File(SERVER_LOCATION + File.separator + image + EXTENSION);

        HttpHeaders header = new HttpHeaders();
        header.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=img.jpg");
        header.add("Cache-Control", "no-cache, no-store, must-revalidate");
        header.add("Pragma", "no-cache");
        header.add("Expires", "0");

        Path path = Paths.get(file.getAbsolutePath());
        ByteArrayResource resource = new ByteArrayResource(Files.readAllBytes(path));

        return ResponseEntity.ok()
                .headers(header)
                .contentLength(file.length())
                .contentType(MediaType.parseMediaType("application/octet-stream"))
                .body(resource);
    }

}
Malay answered 21/2, 2019 at 12:1 Comment(2)
Great worked for me.Veronica
This approach may be suitable for small files, but if dealing with larger files, it would be more efficient to write directly to the OutputStream and avoid loading the entire file into memoryDuprey
S
28

I would suggest using a StreamingResponseBody since with it, the application can write directly to the response (OutputStream), without holding up the Servlet container thread. It is a good approach if you are downloading a very large file.

@GetMapping("download")
public StreamingResponseBody downloadFile(HttpServletResponse response, @PathVariable Long fileId) {

    FileInfo fileInfo = fileService.findFileInfo(fileId);
    response.setContentType(fileInfo.getContentType());
    response.setHeader(
        HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=\"" + fileInfo.getFilename() + "\"");

    return outputStream -> {
        int bytesRead;
        byte[] buffer = new byte[BUFFER_SIZE];
        InputStream inputStream = fileInfo.getInputStream();
        while ((bytesRead = inputStream.read(buffer)) != -1) {
            outputStream.write(buffer, 0, bytesRead);
        }
    };
}

Ps.: When using StreamingResponseBody, it is highly recommended to configure TaskExecutor used in Spring MVC for executing asynchronous requests. TaskExecutor is an interface that abstracts the execution of a Runnable.

More info: https://medium.com/swlh/streaming-data-with-spring-boot-restful-web-service-87522511c071

Sully answered 23/3, 2020 at 22:18 Comment(3)
stackoverflow.com/users/8650621/felipe-desiderati don't we need to close the inputStream ?Allman
No, because it’s auto closeable. Just check that we are returning a lambda, and it will be called after by the Framework.Sully
stackoverflow.com/users/8650621/felipe-desiderati can we use ByteArrayResource if the files are max 10 MB and stored in DB as byte array? or StreamingResponseBody is better option?Allman
T
13

I want to share a simple approach for downloading files with JavaScript (ES6), React and a Spring Boot backend:

  1. Spring boot Rest Controller

Resource from org.springframework.core.io.Resource

    @SneakyThrows
    @GetMapping("/files/{filename:.+}/{extraVariable}")
    @ResponseBody
    public ResponseEntity<Resource> serveFile(@PathVariable String filename, @PathVariable String extraVariable) {

        Resource file = storageService.loadAsResource(filename, extraVariable);
        return ResponseEntity.ok()
               .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + file.getFilename() + "\"")
               .body(file);
    }
  1. React, API call using AXIOS

Set the responseType to arraybuffer to specify the type of data contained in the response.

export const DownloadFile = (filename, extraVariable) => {
let url = 'http://localhost:8080/files/' + filename + '/' + extraVariable;
return axios.get(url, { responseType: 'arraybuffer' }).then((response) => {
    return response;
})};

Final step > downloading
with the help of js-file-download you can trigger browser to save data to file as if it was downloaded.

DownloadFile('filename.extension', 'extraVariable').then(
(response) => {
    fileDownload(response.data, filename);
}
, (error) => {
    // ERROR 
});
Tumescent answered 22/2, 2020 at 11:37 Comment(1)
I ran across this issue and was curious about the enclosure of the CONTENT_DISPOSITION header's filename in double quotes. Turns out that if you have a file with spaces in its name, you won't get the entire filename in the response without the double quotes. Good call, @TumescentFalkner
B
10

If you need to download a huge file from the server's file system, then ByteArrayResource can take all Java heap space. In that case, you can use FileSystemResource

Blither answered 26/5, 2020 at 8:22 Comment(2)
can we use file system resource if the file is stored in byte array in DB? and on click we need to to download that file. Also what is the definition of huge file . my files are 255213 bytesAllman
@Allman I think that a better solution is using ByteArrayResource (see Option 2 here https://mcmap.net/q/151304/-download-a-file-from-spring-boot-rest-service), huge file for example more than 500MBBlither
L
4
    @GetMapping("/downloadfile/{productId}/{fileName}")
public ResponseEntity<Resource> downloadFile(@PathVariable(value = "productId") String productId,
        @PathVariable String fileName, HttpServletRequest request) {
    // Load file as Resource
    Resource resource;

    String fileBasePath = "C:\\Users\\v_fzhang\\mobileid\\src\\main\\resources\\data\\Filesdown\\" + productId
            + "\\";
    Path path = Paths.get(fileBasePath + fileName);
    try {
        resource = new UrlResource(path.toUri());
    } catch (MalformedURLException e) {
        e.printStackTrace();
        return null;
    }

    // Try to determine file's content type
    String contentType = null;
    try {
        contentType = request.getServletContext().getMimeType(resource.getFile().getAbsolutePath());
    } catch (IOException ex) {
        System.out.println("Could not determine file type.");
    }

    // Fallback to the default content type if type could not be determined
    if (contentType == null) {
        contentType = "application/octet-stream";
    }

    return ResponseEntity.ok().contentType(MediaType.parseMediaType(contentType))
            .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + resource.getFilename() + "\"")
            .body(resource);
}

To test it, use postman

http://localhost:8080/api/downloadfile/GDD/1.zip

Lamphere answered 4/3, 2020 at 1:1 Comment(0)
S
0

using Apache IO could be another option for copy the Stream

@RequestMapping(path = "/file/{fileId}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> downloadFile(@PathVariable(value="fileId") String fileId,HttpServletResponse response) throws Exception {

    InputStream yourInputStream = ...
    IOUtils.copy(yourInputStream, response.getOutputStream());
    response.flushBuffer();
    return ResponseEntity.ok().build();
}

maven dependency

    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-io</artifactId>
        <version>1.3.2</version>
    </dependency>
Selfregulated answered 15/9, 2020 at 10:52 Comment(1)
Send back directly InputStreamResource with inputStream. You don't need to copy stream.Catarrhine

© 2022 - 2024 — McMap. All rights reserved.