Spring Boot Upload Multiple Files Examples with Thymeleaf

SpringBoot Upload Single Multiple Files

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.

Related posts:


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.
SpringBoot Upload Download Multiple Files
SpringBoot Upload Download Multiple Files

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

springboot upload download multiple files - coding project structure
Coding Project 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 mappings
  • spring-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 MultipartFile
  • Resource loadFile(String filename) is used to load a file and returns back a result that be wrapped in a Resource object
  • init() is used to create a root location folder for uploading or downloading file systems
  • void deleteAll() is used to delete all files in a root location folder
  • Stream getFiles() is used to get all file names in the root location and returns back to a caller function

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 file
  • uploadSingleFile(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 form
  • uploadMultipartFile(MultipartFile[] files, Model model) is used to handle any http POST request to upload multiple files and then returns back a uploadmultiplefile.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:

Springboot Download File Controller
Springboot Download File Controller
  • 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 downloadFile(@PathVariable String filename) method handles any http POST request with incomming URL /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:

Upload Single File
Upload Single File

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

Upload Multiple Files
Upload Multiple Files

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

List All Uploaded Files
List All Uploaded Files

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

Download all the Uploaded Files
Download all the Uploaded Files
Download File Requests
Download File Requests

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

UploadDownloadMultipartFile

– Github Sourcecode:

GitHub – SpringBoot Upload Download Multiple Files

Further Reading

Related posts:


4 thoughts on “Spring Boot Upload Multiple Files Examples with Thymeleaf”

Leave a Reply

Your email address will not be published. Required fields are marked *