downloadFile(@PathVariable String fileName, HttpServletRequest request) {
53 | // Load file as Resource
54 | Resource resource = fileStorageService.loadFileAsResource(fileName);
55 |
56 | // Try to determine file's content type
57 | String contentType = null;
58 | try {
59 | contentType = request.getServletContext().getMimeType(resource.getFile().getAbsolutePath());
60 | } catch (IOException ex) {
61 | logger.info("Could not determine file type.");
62 | }
63 |
64 | // Fallback to the default content type if type could not be determined
65 | if(contentType == null) {
66 | contentType = "application/octet-stream";
67 | }
68 |
69 | return ResponseEntity.ok()
70 | .contentType(MediaType.parseMediaType(contentType))
71 | .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + resource.getFilename() + "\"")
72 | .body(resource);
73 | }
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/src/main/java/com/example/filedemo/exception/FileStorageException.java:
--------------------------------------------------------------------------------
1 | package com.example.filedemo.exception;
2 |
3 | public class FileStorageException extends RuntimeException {
4 | public FileStorageException(String message) {
5 | super(message);
6 | }
7 |
8 | public FileStorageException(String message, Throwable cause) {
9 | super(message, cause);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/com/example/filedemo/exception/MyFileNotFoundException.java:
--------------------------------------------------------------------------------
1 | package com.example.filedemo.exception;
2 |
3 | import org.springframework.http.HttpStatus;
4 | import org.springframework.web.bind.annotation.ResponseStatus;
5 |
6 | @ResponseStatus(HttpStatus.NOT_FOUND)
7 | public class MyFileNotFoundException extends RuntimeException {
8 | public MyFileNotFoundException(String message) {
9 | super(message);
10 | }
11 |
12 | public MyFileNotFoundException(String message, Throwable cause) {
13 | super(message, cause);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/com/example/filedemo/payload/UploadFileResponse.java:
--------------------------------------------------------------------------------
1 | package com.example.filedemo.payload;
2 |
3 |
4 | public class UploadFileResponse {
5 | private String fileName;
6 | private String fileDownloadUri;
7 | private String fileType;
8 | private long size;
9 |
10 | public UploadFileResponse(String fileName, String fileDownloadUri, String fileType, long size) {
11 | this.fileName = fileName;
12 | this.fileDownloadUri = fileDownloadUri;
13 | this.fileType = fileType;
14 | this.size = size;
15 | }
16 |
17 | public String getFileName() {
18 | return fileName;
19 | }
20 |
21 | public void setFileName(String fileName) {
22 | this.fileName = fileName;
23 | }
24 |
25 | public String getFileDownloadUri() {
26 | return fileDownloadUri;
27 | }
28 |
29 | public void setFileDownloadUri(String fileDownloadUri) {
30 | this.fileDownloadUri = fileDownloadUri;
31 | }
32 |
33 | public String getFileType() {
34 | return fileType;
35 | }
36 |
37 | public void setFileType(String fileType) {
38 | this.fileType = fileType;
39 | }
40 |
41 | public long getSize() {
42 | return size;
43 | }
44 |
45 | public void setSize(long size) {
46 | this.size = size;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/com/example/filedemo/property/FileStorageProperties.java:
--------------------------------------------------------------------------------
1 | package com.example.filedemo.property;
2 |
3 | import org.springframework.boot.context.properties.ConfigurationProperties;
4 |
5 | @ConfigurationProperties(prefix = "file")
6 | public class FileStorageProperties {
7 | private String uploadDir;
8 |
9 | public String getUploadDir() {
10 | return uploadDir;
11 | }
12 |
13 | public void setUploadDir(String uploadDir) {
14 | this.uploadDir = uploadDir;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/com/example/filedemo/service/FileStorageService.java:
--------------------------------------------------------------------------------
1 | package com.example.filedemo.service;
2 |
3 | import com.example.filedemo.exception.FileStorageException;
4 | import com.example.filedemo.exception.MyFileNotFoundException;
5 | import com.example.filedemo.property.FileStorageProperties;
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.core.io.Resource;
8 | import org.springframework.core.io.UrlResource;
9 | import org.springframework.stereotype.Service;
10 | import org.springframework.util.StringUtils;
11 | import org.springframework.web.multipart.MultipartFile;
12 | import java.io.IOException;
13 | import java.net.MalformedURLException;
14 | import java.nio.file.Files;
15 | import java.nio.file.Path;
16 | import java.nio.file.Paths;
17 | import java.nio.file.StandardCopyOption;
18 |
19 | @Service
20 | public class FileStorageService {
21 |
22 | private final Path fileStorageLocation;
23 |
24 | @Autowired
25 | public FileStorageService(FileStorageProperties fileStorageProperties) {
26 | this.fileStorageLocation = Paths.get(fileStorageProperties.getUploadDir())
27 | .toAbsolutePath().normalize();
28 |
29 | try {
30 | Files.createDirectories(this.fileStorageLocation);
31 | } catch (Exception ex) {
32 | throw new FileStorageException("Could not create the directory where the uploaded files will be stored.", ex);
33 | }
34 | }
35 |
36 | public String storeFile(MultipartFile file) {
37 | // Normalize file name
38 | String fileName = StringUtils.cleanPath(file.getOriginalFilename());
39 |
40 | try {
41 | // Check if the file's name contains invalid characters
42 | if(fileName.contains("..")) {
43 | throw new FileStorageException("Sorry! Filename contains invalid path sequence " + fileName);
44 | }
45 |
46 | // Copy file to the target location (Replacing existing file with the same name)
47 | Path targetLocation = this.fileStorageLocation.resolve(fileName);
48 | Files.copy(file.getInputStream(), targetLocation, StandardCopyOption.REPLACE_EXISTING);
49 |
50 | return fileName;
51 | } catch (IOException ex) {
52 | throw new FileStorageException("Could not store file " + fileName + ". Please try again!", ex);
53 | }
54 | }
55 |
56 | public Resource loadFileAsResource(String fileName) {
57 | try {
58 | Path filePath = this.fileStorageLocation.resolve(fileName).normalize();
59 | Resource resource = new UrlResource(filePath.toUri());
60 | if(resource.exists()) {
61 | return resource;
62 | } else {
63 | throw new MyFileNotFoundException("File not found " + fileName);
64 | }
65 | } catch (MalformedURLException ex) {
66 | throw new MyFileNotFoundException("File not found " + fileName, ex);
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | ## MULTIPART (MultipartProperties)
2 | # Enable multipart uploads
3 | spring.servlet.multipart.enabled=true
4 | # Threshold after which files are written to disk.
5 | spring.servlet.multipart.file-size-threshold=2KB
6 | # Max file size.
7 | spring.servlet.multipart.max-file-size=200MB
8 | # Max Request Size
9 | spring.servlet.multipart.max-request-size=215MB
10 |
11 | ## File Storage Properties
12 | # Please change this to the path where you want the uploaded files to be stored.
13 | file.upload-dir=/Users/callicoder/uploads
14 |
--------------------------------------------------------------------------------
/src/main/resources/static/css/main.css:
--------------------------------------------------------------------------------
1 | * {
2 | -webkit-box-sizing: border-box;
3 | -moz-box-sizing: border-box;
4 | box-sizing: border-box;
5 | }
6 |
7 | body {
8 | margin: 0;
9 | padding: 0;
10 | font-weight: 400;
11 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
12 | font-size: 1rem;
13 | line-height: 1.58;
14 | color: #333;
15 | background-color: #f4f4f4;
16 | }
17 |
18 | body:before {
19 | height: 50%;
20 | width: 100%;
21 | position: absolute;
22 | top: 0;
23 | left: 0;
24 | background: #128ff2;
25 | content: "";
26 | z-index: 0;
27 | }
28 |
29 | .clearfix:after {
30 | display: block;
31 | content: "";
32 | clear: both;
33 | }
34 |
35 |
36 | h1, h2, h3, h4, h5, h6 {
37 | margin-top: 20px;
38 | margin-bottom: 20px;
39 | }
40 |
41 | h1 {
42 | font-size: 1.7em;
43 | }
44 |
45 | a {
46 | color: #128ff2;
47 | }
48 |
49 | button {
50 | box-shadow: none;
51 | border: 1px solid transparent;
52 | font-size: 14px;
53 | outline: none;
54 | line-height: 100%;
55 | white-space: nowrap;
56 | vertical-align: middle;
57 | padding: 0.6rem 1rem;
58 | border-radius: 2px;
59 | transition: all 0.2s ease-in-out;
60 | cursor: pointer;
61 | min-height: 38px;
62 | }
63 |
64 | button.primary {
65 | background-color: #128ff2;
66 | box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.12);
67 | color: #fff;
68 | }
69 |
70 | input {
71 | font-size: 1rem;
72 | }
73 |
74 | input[type="file"] {
75 | border: 1px solid #128ff2;
76 | padding: 6px;
77 | max-width: 100%;
78 | }
79 |
80 | .file-input {
81 | width: 100%;
82 | }
83 |
84 | .submit-btn {
85 | display: block;
86 | margin-top: 15px;
87 | min-width: 100px;
88 | }
89 |
90 | @media screen and (min-width: 500px) {
91 | .file-input {
92 | width: calc(100% - 115px);
93 | }
94 |
95 | .submit-btn {
96 | display: inline-block;
97 | margin-top: 0;
98 | margin-left: 10px;
99 | }
100 | }
101 |
102 |
103 | .upload-container {
104 | max-width: 750px;
105 | margin-left: auto;
106 | margin-right: auto;
107 | background-color: #fff;
108 | box-shadow: 0 1px 11px rgba(0, 0, 0, 0.27);
109 | margin-top: 60px;
110 | min-height: 400px;
111 | position: relative;
112 | padding: 20px;
113 | }
114 |
115 | .upload-header {
116 | border-bottom: 1px solid #ececec;
117 | }
118 |
119 | .upload-header h2 {
120 | font-weight: 500;
121 | }
122 |
123 | .single-upload {
124 | padding-bottom: 20px;
125 | margin-bottom: 20px;
126 | border-bottom: 1px solid #e8e8e8;
127 | }
128 |
129 | .upload-response {
130 | overflow-x: hidden;
131 | word-break: break-all;
132 | }
133 |
--------------------------------------------------------------------------------
/src/main/resources/static/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Spring Boot File Upload / Download Rest API Example
6 |
7 |
8 |
9 |
12 |
13 |
16 |
17 |
18 |
Upload Single File
19 |
23 |
27 |
28 |
29 |
Upload Multiple Files
30 |
34 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/src/main/resources/static/js/main.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var singleUploadForm = document.querySelector('#singleUploadForm');
4 | var singleFileUploadInput = document.querySelector('#singleFileUploadInput');
5 | var singleFileUploadError = document.querySelector('#singleFileUploadError');
6 | var singleFileUploadSuccess = document.querySelector('#singleFileUploadSuccess');
7 |
8 | var multipleUploadForm = document.querySelector('#multipleUploadForm');
9 | var multipleFileUploadInput = document.querySelector('#multipleFileUploadInput');
10 | var multipleFileUploadError = document.querySelector('#multipleFileUploadError');
11 | var multipleFileUploadSuccess = document.querySelector('#multipleFileUploadSuccess');
12 |
13 | function uploadSingleFile(file) {
14 | var formData = new FormData();
15 | formData.append("file", file);
16 |
17 | var xhr = new XMLHttpRequest();
18 | xhr.open("POST", "/uploadFile");
19 |
20 | xhr.onload = function() {
21 | console.log(xhr.responseText);
22 | var response = JSON.parse(xhr.responseText);
23 | if(xhr.status == 200) {
24 | singleFileUploadError.style.display = "none";
25 | singleFileUploadSuccess.innerHTML = "File Uploaded Successfully.
DownloadUrl : " + response.fileDownloadUri + "
";
26 | singleFileUploadSuccess.style.display = "block";
27 | } else {
28 | singleFileUploadSuccess.style.display = "none";
29 | singleFileUploadError.innerHTML = (response && response.message) || "Some Error Occurred";
30 | }
31 | }
32 |
33 | xhr.send(formData);
34 | }
35 |
36 | function uploadMultipleFiles(files) {
37 | var formData = new FormData();
38 | for(var index = 0; index < files.length; index++) {
39 | formData.append("files", files[index]);
40 | }
41 |
42 | var xhr = new XMLHttpRequest();
43 | xhr.open("POST", "/uploadMultipleFiles");
44 |
45 | xhr.onload = function() {
46 | console.log(xhr.responseText);
47 | var response = JSON.parse(xhr.responseText);
48 | if(xhr.status == 200) {
49 | multipleFileUploadError.style.display = "none";
50 | var content = "All Files Uploaded Successfully
";
51 | for(var i = 0; i < response.length; i++) {
52 | content += "DownloadUrl : " + response[i].fileDownloadUri + "
";
53 | }
54 | multipleFileUploadSuccess.innerHTML = content;
55 | multipleFileUploadSuccess.style.display = "block";
56 | } else {
57 | multipleFileUploadSuccess.style.display = "none";
58 | multipleFileUploadError.innerHTML = (response && response.message) || "Some Error Occurred";
59 | }
60 | }
61 |
62 | xhr.send(formData);
63 | }
64 |
65 | singleUploadForm.addEventListener('submit', function(event){
66 | var files = singleFileUploadInput.files;
67 | if(files.length === 0) {
68 | singleFileUploadError.innerHTML = "Please select a file";
69 | singleFileUploadError.style.display = "block";
70 | }
71 | uploadSingleFile(files[0]);
72 | event.preventDefault();
73 | }, true);
74 |
75 |
76 | multipleUploadForm.addEventListener('submit', function(event){
77 | var files = multipleFileUploadInput.files;
78 | if(files.length === 0) {
79 | multipleFileUploadError.innerHTML = "Please select at least one file";
80 | multipleFileUploadError.style.display = "block";
81 | }
82 | uploadMultipleFiles(files);
83 | event.preventDefault();
84 | }, true);
85 |
86 |
--------------------------------------------------------------------------------
/src/test/java/com/example/filedemo/FileDemoApplicationTests.java:
--------------------------------------------------------------------------------
1 | package com.example.filedemo;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import org.springframework.boot.test.context.SpringBootTest;
5 |
6 | @SpringBootTest
7 | public class FileDemoApplicationTests {
8 |
9 | @Test
10 | public void contextLoads() {
11 | }
12 |
13 | }
14 |
--------------------------------------------------------------------------------