download a file from Spring boot rest service

JavaSpringRest

Java Problem Overview


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?

Java Solutions


Solution 1 - Java

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);
}

Solution 2 - Java

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);
    }

}

Solution 3 - Java

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

Solution 4 - Java

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);
    }

> 2. 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 
});

Solution 5 - Java

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

Solution 6 - Java

	@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

Solution 7 - Java

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>

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionkiranView Question on Stackoverflow
Solution 1 - JavafateddyView Answer on Stackoverflow
Solution 2 - JavaRajeshView Answer on Stackoverflow
Solution 3 - JavaFelipe DesideratiView Answer on Stackoverflow
Solution 4 - JavafetahokeyView Answer on Stackoverflow
Solution 5 - JavaTaras MelonView Answer on Stackoverflow
Solution 6 - JavaFeng ZhangView Answer on Stackoverflow
Solution 7 - JavaJPGView Answer on Stackoverflow