SpringBoot upload multiple files is one of the most common tasks with Java developers. So in the tutorial, I will guide you step by step how to build a “Spring Boot Upload Multiple Files” (SpringBoot Download Multiple Files) by an examples and running sourcecode:
– I draw an overview diagram architecture of SpringBoot application to upload/download files.
– I implement Upload Download SpringBoot RestAPI controllers with a helper File Services.
– I create a testsuites for upload/download single and multiple files.
Here is a structure of the tutorial:
Youtube Video Guide – SpringBoot RestApi File Upload
Overview Diagram Architecture – How to build Spring Boot Upload Multiple Files?
Below is an architecuture of our project. We need 3 main components to do upload/download files:
- Upload Controllers are used to handle all http POST requests to upload files to SpringBoot server.
- Download Controller is used to handle all http GET requests to download files from File System through SpringBoot server.
- File Services are used to define specific functions to store and retrieve files.

To implement the design, we create a SpringBoot project as below structure:

controller
package is used to define Requestmaping Controller classes to POST/GET upload files.service
package is used to implement specific functions to store and retrieve file systems.templates
folder contains Thymeleaf Html views to upload/download Multipart files.
Create a SpringBoot project
We use SpringToolSuite to create a SpringBoot with 2 dependencies:
spring-boot-starter-web
is used to handle any http request mappingsspring-boot-starter-thymeleaf
is used to create a Html view for upload/download files
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Implement File Service
For manipulating with File Systems, we define a FileStorage
interface APIs, that is used to store and retrieve information of files by Java language. With FileStorage.java
, we define 5 function APIs:
store(MultipartFile file)
is used to save MultipartFileResource loadFile(String filename)
is used to load a file and returns back a result that be wrapped in aResource
objectinit()
is used to create a root location folder for uploading or downloading file systemsvoid deleteAll()
is used to delete all files in a root location folderStream
is used to get all file names in the root location and returns back to a caller functiongetFiles()
package com.loizenjava.uploaddownloadfiles.service;
import java.nio.file.Path;
import java.util.stream.Stream;
import org.springframework.core.io.Resource;
import org.springframework.web.multipart.MultipartFile;
/**
* Copyright by https://loizenjava.com
* @author loizenjava.com
*
*/
public interface FileStorage {
public void store(MultipartFile file);
public Resource loadFile(String filename);
public void deleteAll();
public void init();
public Stream getFiles();
}
Now we create a class FileStorageImpl.java
to implement the defined APIs in FileStorage.java
interface:
package com.loizenjava.uploaddownloadfiles.service;
import java.io.IOException;
import java.net.MalformedURLException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Stream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.stereotype.Service;
import org.springframework.util.FileSystemUtils;
import org.springframework.web.multipart.MultipartFile;
/**
* Copyright by https://loizenjava.com
* @author loizenjava.com
* Please Do NOT re-post
*/
@Service
public class FileStorageImpl implements FileStorage {
Logger log = LoggerFactory.getLogger(this.getClass().getName());
private final Path rootLocation = Paths.get("storages");
@Override
public void store(MultipartFile file) {
try {
Files.copy(file.getInputStream(), this.rootLocation.resolve(file.getOriginalFilename()));
} catch (Exception e) {
throw new RuntimeException("Error! -> message = " + e.getMessage());
}
}
@Override
public Resource loadFile(String filename) {
try {
Path file = rootLocation.resolve(filename);
Resource resource = new UrlResource(file.toUri());
if (resource.exists() || resource.isReadable()) {
return resource;
} else {
throw new RuntimeException("FAIL!");
}
} catch (MalformedURLException e) {
throw new RuntimeException("Error! -> message = " + e.getMessage());
}
}
@Override
public void deleteAll() {
FileSystemUtils.deleteRecursively(rootLocation.toFile());
}
@Override
public void init() {
try {
Files.createDirectory(rootLocation);
} catch (IOException e) {
throw new RuntimeException("Could not initialize file storage!");
}
}
@Override
public Stream getFiles() {
try {
return Files.walk(this.rootLocation, 1).filter(path -> !path.equals(this.rootLocation))
.map(this.rootLocation::relativize);
} catch (IOException e) {
throw new RuntimeException("Error! -> message = " + e.getMessage());
}
}
}
Implement Upload Single File Controller
For uploading a single file to SpringBoot server, we implement a controller UploadSingleFileController.java
class. The Controller implements 2 methods for Requestmapping:
index()
method defined with@GetMapping
annotation that is used to return back a HTML form view for uploading a single fileuploadSingleFile(MultipartFile file, Model model)
is used to handle a POST Http request to upload a file
package com.loizenjava.uploaddownloadfiles.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import com.loizenjava.uploaddownloadfiles.service.FileStorage;
@Controller
public class UploadSingleFileController {
@Autowired
FileStorage fileStorage;
@GetMapping("/uploadsinglefile")
public String index() {
return "uploadsinglefile.html";
}
@PostMapping("/uploadsinglefile")
public String uploadSingleFile(@RequestParam("uploadfile") MultipartFile file, Model model) {
if(file.getOriginalFilename().isEmpty()) {
model.addAttribute("message", "Fail -> Upload filename is empty! Please check it!");
return "uploadsinglefile.html";
}
try {
fileStorage.store(file);
model.addAttribute("message", "Successfully! -> upload filename: " + file.getOriginalFilename());
} catch (Exception e) {
model.addAttribute("message", "Fail! -> Existed Uploaded Filename: " + file.getOriginalFilename());
}
return "uploadsinglefile.html";
}
}
uploadSingleFile
method uses FileStorage
service to save upload file to disk and returns back a html view uploadsinglefile.html
.
MultipartFile
is a representation of an uploaded file received in a multipart request. The file contents are either stored in memory or temporarily on disk. In either case, the user is responsible for copying file contents to a session-level or persistent store as and if desired. The temporary storage will be cleared at the end of request processing. (via Spring docs)
Create Thymeleaf Upload Single File
Now we create a uploadsinglefile.html
view with Bootstrap framework to define a upload file form and use Thymeleaf engine to parse a message
attribute returned from uploadSingleFile()
method.
<!DOCTYPE html>
<html lang="en">
<head>
<title>Upload Files</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
<div class="row h-100 justify-content-center align-items-center">
<div class="col-sm-5"
style="background-color:#e6ecff; margin:5px;
padding:7px; border: solid 1px black; border-radius: 3px">
<h3>Upload File to SpringBoot Backend</h3>
<form method="POST" enctype="multipart/form-data" id="uploadForm">
<div class="form-group">
<label for="uploadfile">Upload File:</label>
<input type="file" class="form-control" placeholder="Upload File" id="uploadfile" name="uploadfile">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
<a href="/api/download/files" class="btn btn-primary" role="button">Files</a>
</form>
</div>
</div>
<div th:if="${message}"
class="row h-100 justify-content-center align-items-center">
<div class="row col-sm-5"
style="background-color: yellow; margin:5px; padding: 7px; border: solid 1px black; border-radius: 3px">
<span th:text="${message}"/>
</div>
</div>
</div>
</body>
</html>
Implement Upload Multiple File Controller
We implement a UploadMultipleFileController.java
class controller to upload multiple files. The class we create 2 methods:
String index()
method is used to return a html view that defines a upload multiple files formuploadMultipartFile(MultipartFile[] files, Model model)
is used to handle any http POST request to upload multiple files and then returns back auploadmultiplefile.html
view with uploaded message attribute.
package com.loizenjava.uploaddownloadfiles.controller;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import com.loizenjava.uploaddownloadfiles.service.FileStorage;
/**
* Copyright by https://loizenjava.com
* @author loizenjava.com
*
*/
@Controller
public class UploadMultipleFileController {
@Autowired
FileStorage fileStorage;
@GetMapping("/")
public String index() {
return "uploadmultiplefile.html";
}
@PostMapping("/")
public String uploadMultipartFile(@RequestParam("uploadfile") MultipartFile[] files, Model model) {
List messages = new ArrayList();
for(MultipartFile file: files) {
try {
if(file.getOriginalFilename().isEmpty()) {
messages.add("Fail! -> Empty File Name");
continue;
}
fileStorage.store(file);
messages.add("Successfully! -> upload filename: " + file.getOriginalFilename());
} catch (Exception e) {
messages.add("Fail! -> Existed Uploaded Filename: " + file.getOriginalFilename());
}
}
model.addAttribute("messages", messages);
return "uploadmultiplefile.html";
}
}
Create Thymeleaf Upload Multiple File
We create a html view uploadmultiplefile.html
with Bootstrap framework to define a upload form having 3 file input fields and uses Thymeleaf engine to handle messages
attribute returned from uploadMultipartFile()
method.
<!DOCTYPE html>
<html lang="en">
<head>
<title>Upload Files</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
<div class="row h-100 justify-content-center align-items-center">
<div class="col-sm-5"
style="background-color:#e6ecff; margin:5px;
padding:7px; border: solid 1px black; border-radius: 3px">
<h3>Upload File to SpringBoot Backend</h3>
<form method="POST" enctype="multipart/form-data" id="uploadForm">
<div class="form-group">
<label for="uploadfile2">File 1:</label>
<input type="file" class="form-control" placeholder="Upload File" id="uploadfile1" name="uploadfile">
</div>
<div class="form-group">
<label for="uploadfile2">File 2:</label>
<input type="file" class="form-control" placeholder="Upload File" id="uploadfile2" name="uploadfile">
</div>
<div class="form-group">
<label for="uploadfile3">File 3:</label>
<input type="file" class="form-control" placeholder="Upload File" id="uploadfile3" name="uploadfile">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
<a href="/api/download/files" class="btn btn-primary" role="button">Files</a>
</form>
</div>
</div>
<div th:if="${messages!=null and !messages.isEmpty()}"
class="row h-100 justify-content-center align-items-center">
<div class="row col-sm-5"
style="background-color: yellow; margin: 5px; padding: 7px; border: solid 1px black; border-radius: 3px">
<ul th:each="message : ${messages}">
<li th:text="${message}"></li>
</ul>
</div>
</div>
</div>
</body>
</html>
Implement Download File Controller
We implement a DownloadController.java
class with 2 requestmapping methods for downloading files from SpringBoot server:

String getListFiles(Model model)
method handles any http GET requests with URL/api/download/files
to get a list of file names under uploaded root location folder.ResponseEntity
method handles any http POST request with incomming URLdownloadFile(@PathVariable String filename) /api/download/file/{filename}
to download a file system through the SpringBoot server.
package com.loizenjava.uploaddownloadfiles.controller;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
import com.loizenjava.uploaddownloadfiles.info.FileInfo;
import com.loizenjava.uploaddownloadfiles.service.FileStorage;
/**
* Copyright by https://loizenjava.com
* @author loizenjava.com
*
*/
@Controller
@RequestMapping("/api/download")
public class DownloadController {
@Autowired
FileStorage fileStorage;
/*
* Retrieve Files' Information
*/
@GetMapping("/files")
public String getListFiles(Model model) {
List<FileInfo> fileInfos = fileStorage.getFiles().map(path -> {
String filename = path.getFileName().toString();
String url = MvcUriComponentsBuilder
.fromMethodName(DownloadController.class, "downloadFile",
path.getFileName().toString()).build()
.toString();
return new FileInfo(filename, url);
}).collect(Collectors.toList());
model.addAttribute("files", fileInfos);
return "files.html";
}
/*
* Download Files
*/
@GetMapping("/file/{filename}")
public ResponseEntity<Resource> downloadFile(@PathVariable String filename) {
Resource file = fileStorage.loadFile(filename);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + file.getFilename() + "\"")
.body(file);
}
}
Create Thymeleaf Download File
The method getListFiles(Model model)
returns back a Html view form files.html
that contains needed information of each file in the root location folder for showing to client.
We define a Java class FileInfo.java
to include a name of a file and a http link to download the file.
package com.loizenjava.uploaddownloadfiles.info;
/**
* Copyright by https://loizenjava.com
* @author loizenjava.com
*
*/
public class FileInfo {
private String filename;
private String url;
public FileInfo(String filename, String url) {
this.filename = filename;
this.url = url;
}
//getters & setters...
}
Now we build a files.html
view to list all information of each file in the uploaded root location folder with Bootstrap framework and Thymeleaf engine:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Uploaded Files</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
<div class="row h-100 justify-content-center align-items-center">
<div class="col-md-7 table-responsive"
style="background-color: #eeffe6; magin:5px; padding:7px;
border:solid 1px black">
<h2>Uploaded Files</h2>
<table id="customerTable" class="table">
<thead>
<tr>
<th>No</th>
<th>Filename</th>
<th>Download</th>
</tr>
</thead>
<tbody>
<tr th:each="file, rowStat: ${files}">
<td th:text="${rowStat.count}">1</td>
<td th:text="${file.filename}">Filename</td>
<td><a th:href="${file.url}">Link</a></td>
</tr>
</tbody>
</table>
<div>
<a href="/" class="btn btn-primary" role="button">Upload Multiple Files</a>
<a href="/uploadsinglefile" class="btn btn-primary" role="button">Upload Single File</a>
</div>
</div>
</div>
</div>
</body>
</html>
Initial Spring Boot App
Before running and testing the project, we need to add some code for creating the root location folder where is a place to store uploaded files. So we modify some code in a main class of SpringBoot project with CommandLineRunner
interface:
package com.loizenjava.uploaddownloadfiles;
import javax.annotation.Resource;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import com.loizenjava.uploaddownloadfiles.service.FileStorage;
@SpringBootApplication
public class SpringBootUploadDownloadMultipartFileApplication implements CommandLineRunner {
@Resource
FileStorage fileStorage;
public static void main(String[] args) {
SpringApplication.run(SpringBootUploadDownloadMultipartFileApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
fileStorage.deleteAll();
fileStorage.init();
}
}
Testing
Now I define a set of testcase for “Spring Boot Upload Multiple Files (Spring Boot Downloadload Multiple Files)” using Spring Web and RestClient:
– Make a request with incomming URL http://localhost:8080/uploadsinglefile
to upload any single file:

– Make a request with incomming URL http://localhost:8080/
to upload multiple files:

– Make a http GET request with incomming URL http://localhost:8080/api/download/files
to list all files:

– From List Files view, click to a link to download a file:


SourceCode – Spring Boot Upload Multiple Files
– Running sourcecode for the tutorial: “SpringBoot Upload Download Multiple Files Examples with Thymeleaf”. With 3 functions:
- SpringBoot Upload Single File
- Spring Boot Upload Multiple Files
- SpringBoot Download Files
– Github Sourcecode:
GitHub – SpringBoot Upload Download Multiple Files
Thx pro!
So good , keep work!
Great man!
Thanks for your own work on this website.