Learn how to upload folders in a spring boot application with this article.
In the browser, we usually select the file to be uploaded by the <input type="file"/>
tag. By default, it can only select one file or multiple files, not the whole folder directly.
If the <input/>
tag has an attribute called webkitdirectory
, the user can select the entire folder and the browser will upload all the files under the folder to the server at once.
The HTMLInputElement.webkitdirectory
is a property that reflects the webkitdirectory
HTML attribute and indicates that the <input>
element should let the user select directories instead of files. When a directory is selected, the directory and its entire hierarchy of contents are included in the set of selected items. The selected file system entries can be obtained using the webkitEntries
property.
https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/webkitdirectory
HTML
Create an index.html
with the following content.
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Uploading a folder to the server</title>
</head>
<body>
<form action="/upload" method="POST" enctype="multipart/form-data">
<input type="file" name="file" multiple webkitdirectory/>
<input type="submit"/>
</form>
</body>
</html>
|
The browser will submit the relative path of the file in the folder as the file name to the server, and the server can create the file locally based on this relative path, thus ensuring consistency with the directory structure of the client.
Controller
The server needs to write each file to a local folder according to the client’s file layout.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.io.FilenameUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
*
* @author KevinBlandy
*
*/
@RestController
@RequestMapping("/upload")
public class UploadController {
private static final Logger LOGGER = LoggerFactory.getLogger(UploadController.class);
/**
* By default, the public folder in the working directory is a static resource directory, which can be accessed directly by the client.
*/
private static final Path PUBLIC_DIR = Paths.get(System.getProperty("user.dir"), "public");
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public String upload (HttpServletRequest request) throws IOException, ServletException{
for (var part : request.getParts()) {
// The name of the file is the relative path of the file in the client folder
// Convert the client path separator to the server file path separator.
String fileName = FilenameUtils.separatorsToSystem(part.getSubmittedFileName());
// Resolve the absolute path to the file based on the public folder
Path file = PUBLIC_DIR.resolve(fileName);
// Try to create the folder where the file is located
if (Files.notExists(file.getParent())) {
Files.createDirectories(file.getParent());
}
// Write data to file
try (var inputStream = part.getInputStream()){
Files.copy(inputStream, file, StandardCopyOption.REPLACE_EXISTING);
}
LOGGER.info("write file: [{}] {}", part.getSize(), file);
}
return "ok";
}
}
|
FilenameUtils
is a tool class from commons-io to unify file separators between different operating systems.
1
2
3
4
5
|
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
|
Also you may need to configure some configuration in application.yml about file uploads.
1
2
3
4
5
6
7
8
|
spring:
servlet:
multipart:
enabled: true
max-file-size: -1 # No file size limit
max-request-size: -1 # No limit on request size
location: ${java.io.tmpdir}
file-size-threshold: 10KB
|
Testing
Put index.html
in the public folder of the spring boot application and start the application.
Open your browser and visit: http://localhost:8080
Click the Choose Files
button to select a folder. A warning dialog box will pop up in your browser telling you that this uploads all the files in the folder.
After clicking the Upload
button, you can see the number of files in the folder next to the Choose files
button.
The folder foo
that is uploaded in this demo has the following structure.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
C:\Users\KevinBlandy\Desktop\foo>tree /F
...
C:.
│ 1.txt
│ 2.txt
│
└─bar
│ 3.txt
│
└─temp
│ 4.txt
│
└─spring
main.java
|
Click the Submit
button to submit to the server. And observe the log output from the server’s console.
1
2
3
4
5
|
2022-11-29 13:16:58.249 INFO 6312 --- [nio-8080-exec-2] i.s.web.controller.UploadController : write file: [1] C:\eclipse\eclipse-jee-2022-09-R-win32-x86_64\project\springcloud-cache\public\foo\1.txt
2022-11-29 13:16:58.262 INFO 6312 --- [nio-8080-exec-2] i.s.web.controller.UploadController : write file: [6] C:\eclipse\eclipse-jee-2022-09-R-win32-x86_64\project\springcloud-cache\public\foo\2.txt
2022-11-29 13:16:58.276 INFO 6312 --- [nio-8080-exec-2] i.s.web.controller.UploadController : write file: [0] C:\eclipse\eclipse-jee-2022-09-R-win32-x86_64\project\springcloud-cache\public\foo\bar\3.txt
2022-11-29 13:16:58.288 INFO 6312 --- [nio-8080-exec-2] i.s.web.controller.UploadController : write file: [4] C:\eclipse\eclipse-jee-2022-09-R-win32-x86_64\project\springcloud-cache\public\foo\bar\temp\4.txt
2022-11-29 13:16:58.290 INFO 6312 --- [nio-8080-exec-2] i.s.web.controller.UploadController : write file: [2081] C:\eclipse\eclipse-jee-2022-09-R-win32-x86_64\project\springcloud-cache\public\foo\bar\temp\spring\main.java
|
As you can see from the logs, the server has successfully written all the files to the expected directory according to the layout of the client files.
Check the folder uploaded by the client to the server, everything is OK.
Asynchronous Upload
You can also use Javascript to upload a folder asynchronously, and you can do things like filter the uploaded files.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
document.querySelector('input[name="file"]').addEventListener('change', e => {
let formData = new FormData();
// Iterate through every file in the folder.
for (let file of e.target.files){
// The file.webkitRelativePath property is the relative path of the file in the directory.
// The browser will submit file.webkitRelativePath as the name of the file to the server.
formData.append("file", file);
}
fetch('/upload', {
method: 'POST',
body: formData
}).then(response => {
if(response.ok){
response.text().then(payload => {
console.log(payload);
});
}
}).catch(error => {
console.error(error);
});
});
|