In this tutorial, I will show you how to upload multiple files with Spring Boot, Thymeleaf and Bootstrap. We also use Spring Web MultipartFile
interface to handle HTTP multi-part requests and display list of uploaded files.
Related Posts:
– Spring Boot multiple file Upload Rest API example
– Spring Boot Delete File example with Thymeleaf
Spring Boot Upload/Download File to/from Database example
– Spring Boot: Upload/Import Excel file data into MySQL Database
– Spring Boot: Upload/Import CSV file data into MySQL Database
– Angular + Spring Boot: File upload example
– React + Spring Boot: File upload example
Deployment: Deploy Spring Boot App on AWS – Elastic Beanstalk
Contents
- Overview
- Technology
- Project Structure
- Create & Setup Spring Boot project
- Create Service for File Storage
- Define Data Model
- Create MVC Controller
- Setup the Template
- Implement Multiple File Upload function
- Display List of Files
- Configure Multipart File
- Handle Multiple File Upload Exception
- Initialize Storage
- Run the Project
- Conclusion
- Source Code
- Further Reading
Spring Boot Multiple File Upload with Thymeleaf Overview
Our Spring Boot + Thymeleaf Multiple File Upload example will have following features:
- uploading Multiple Files to a static folder in the Server
- downloading File from server with the link
- getting list of Files’ information (file name & url)
– Here is the Multiple File Upload Form:
– If one of the Files exceeds specific maximum size:
– If some files have the same name with uploaded files:
– This is the static folder that stores all uploaded files:
– You can see list of uploaded files with download links:
In this tutorial, I don’t explain way to delete File. If you want to know that, just visit:
Spring Boot Delete File example with Thymeleaf
Or adding Pagination with following tutorial:
Spring Boot Thymeleaf Pagination example
Technology
- Java 8
- Spring Boot 2.7 (with Spring Web MVC, Thymeleaf)
- Maven 3.6.1
- Bootstrap 4
- jQuery 3.6.1
Project Structure
Let me explain it briefly.
– FileInfo
contains information of the uploaded file.
– FilesStorageService
helps us to initialize storage, save new files, load file, get list of Files’ info, delete files.
– FileController
uses FilesStorageService
to handle multiple file upload/download and template requests.
– FileUploadExceptionAdvice
handles exception when the controller processes file upload.
– template
stores HTML template files for the project.
– application.properties contains configuration for Servlet Multipart.
– uploads is the static folder for storing files.
– pom.xml for Spring Boot dependency.
Create & Setup Spring Boot Multiple File Upload project
Use Spring web tool or your development tool (Spring Tool Suite, Eclipse, Intellij) to create a Spring Boot project.
Then open pom.xml and add dependencies for Spring Web, Thymeleaf, Bootstrap, Jquery:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
<version>4.6.2</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.6.1</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>webjars-locator-core</artifactId>
</dependency>
Create Service for File Storage
First we need an interface that will be autowired in the Controller.
In service folder, create FilesStorageService
interface like following code:
service/FilesStorageService.java
package com.bezkoder.spring.thymeleaf.file.upload.service;
import java.nio.file.Path;
import java.util.stream.Stream;
import org.springframework.core.io.Resource;
import org.springframework.web.multipart.MultipartFile;
public interface FilesStorageService {
public void init();
public void save(MultipartFile file);
public Resource load(String filename);
public void deleteAll();
public Stream<Path> loadAll();
}
Now we create implementation of the interface.
service/FilesStorageServiceImpl.java
package com.bezkoder.spring.thymeleaf.file.upload.service;
import java.io.IOException;
import java.net.MalformedURLException;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Stream;
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;
@Service
public class FilesStorageServiceImpl implements FilesStorageService {
private final Path root = Paths.get("./uploads");
@Override
public void init() {
try {
Files.createDirectories(root);
} catch (IOException e) {
throw new RuntimeException("Could not initialize folder for upload!");
}
}
@Override
public void save(MultipartFile file) {
try {
Files.copy(file.getInputStream(), this.root.resolve(file.getOriginalFilename()));
} catch (Exception e) {
if (e instanceof FileAlreadyExistsException) {
throw new RuntimeException("Filename already exists.");
}
throw new RuntimeException(e.getMessage());
}
}
@Override
public Resource load(String filename) {
try {
Path file = root.resolve(filename);
Resource resource = new UrlResource(file.toUri());
if (resource.exists() || resource.isReadable()) {
return resource;
} else {
throw new RuntimeException("Could not read the file!");
}
} catch (MalformedURLException e) {
throw new RuntimeException("Error: " + e.getMessage());
}
}
@Override
public void deleteAll() {
FileSystemUtils.deleteRecursively(root.toFile());
}
@Override
public Stream<Path> loadAll() {
try {
return Files.walk(this.root, 1).filter(path -> !path.equals(this.root)).map(this.root::relativize);
} catch (IOException e) {
throw new RuntimeException("Could not load the files!");
}
}
}
Create Controller for Multiple File Upload
In controller package, we create FileController
.
controller/FileController.java
package com.bezkoder.spring.thymeleaf.file.upload.controller;
// ...
import com.bezkoder.spring.thymeleaf.file.upload.service.FilesStorageService;
@Controller
public class FileController {
@Autowired
FilesStorageService storageService;
@GetMapping("/")
public String homepage() {
return "redirect:/files";
}
@GetMapping("/files/new")
public String newFile(Model model) {
return "upload_form";
}
@PostMapping("/files/upload")
public String uploadFiles(Model model, @RequestParam("files") MultipartFile[] files) {
...
return "upload_form";
}
@GetMapping("/files")
public String getListFiles(Model model) {
...
return "files";
}
@GetMapping("/files/{filename:.+}")
public ResponseEntity<Resource> getFile(@PathVariable String filename) {
...
// return File
}
}
– @Controller
annotation is used to define a controller.
– @GetMapping
and @PostMapping
annotation is for mapping HTTP GET & POST requests onto specific handler methods and returning appropriate template files.
– We use @Autowired
to inject implementation of FilesStorageService
bean to local variable.
Setup the Template
In src/main/resources folder, create folder and file as following structure:
Header and Footer
We will use Thymeleaf Fragments (th:fragment
) to reuse some common parts such as header and footer.
Let’s write HTML code for them.
fragments/footer.html
<footer class="text-center">
Copyright © BezKoder
</footer>
And header that contains the navigation bar:
fragments/header.html
<header th:fragment="header">
<nav class="navbar navbar-expand-md bg-dark navbar-dark mb-3">
<a class="navbar-brand" th:href="@{/files}">
BezKoder
</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#topNavbar">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="topNavbar">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" th:href="@{/files/new}">Upload</a>
</li>
<li class="nav-item">
<a class="nav-link" th:href="@{/files}">Files</a>
</li>
</ul>
</div>
</nav>
</header>
Now we need to create HTML files, then import Thymeleaf fragments, Bootstrap, jQuery and Font Awesome.
Multiple File Upload Form
upload_form.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0" />
<title>Thymeleaf Multiple File Upload example</title>
<link rel="stylesheet" type="text/css" th:href="@{/webjars/bootstrap/css/bootstrap.min.css}" />
<script type="text/javascript" th:src="@{/webjars/jquery/jquery.min.js}"></script>
<script type="text/javascript" th:src="@{/webjars/bootstrap/js/bootstrap.min.js}"></script>
</head>
<body>
<div th:replace="fragments/header :: header"></div>
-- Multiple file upload form --
<div th:replace="fragments/footer :: footer"></div>
</body>
</html>
List of Files
files.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0" />
<title>Thymeleaf Multiple File Upload example</title>
<link rel="stylesheet" type="text/css" th:href="@{/webjars/bootstrap/css/bootstrap.min.css}" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.0/css/all.min.css"
integrity="sha512-xh6O/CkQoPOWDdYTDqeRdPCVd1SpvCA9XXcUnZS2FmJNp1coAFzvtCN9BmamE+4aHK8yyUHUSCcJHgXloTyT2A=="
crossorigin="anonymous" referrerpolicy="no-referrer" />
<script type="text/javascript" th:src="@{/webjars/jquery/jquery.min.js}"></script>
<script type="text/javascript" th:src="@{/webjars/bootstrap/js/bootstrap.min.js}"></script>
</head>
<body>
<div th:replace="fragments/header :: header"></div>
-- list of files display --
<div th:replace="fragments/footer :: footer"></div>
</body>
</html>
Implement Multiple File Upload function
We use @GetMapping
and @PostMapping
annotation is for mapping HTTP GET & POST requests onto specific handler methods:
- GET /files/new:
newFile()
– return upload_form.html template - POST /files/upload:
uploadFiles()
– upload Multiple Files
The main method is uploadFiles()
in which we use MultipartFile[] files
as an argument, and Java 8 Stream API to work with each file in the array.
FileController.java
import com.bezkoder.spring.thymeleaf.file.upload.service.FilesStorageService;
@Controller
public class FileController {
@Autowired
FilesStorageService storageService;
@GetMapping("/files/new")
public String newFile(Model model) {
return "upload_form";
}
@PostMapping("/files/upload")
public String uploadFiles(Model model, @RequestParam("files") MultipartFile[] files) {
List<String> messages = new ArrayList<>();
Arrays.asList(files).stream().forEach(file -> {
try {
storageService.save(file);
messages.add(file.getOriginalFilename() + " [Successful]");
} catch (Exception e) {
messages.add(file.getOriginalFilename() + " <Failed> - " + e.getMessage());
}
});
model.addAttribute("messages", messages);
return "upload_form";
}
}
upload_form.html
<form
id="uploadForm"
method="post"
th:action="@{/files/upload}"
enctype="multipart/form-data">
<input type="file" multiple name="files" />
<button class="btn btn-sm btn-outline-success float-right" type="submit">
Upload
</button>
</form>
<div th:if="${messages != null}" class="alert alert-secondary alert-dismissible fade show message mt-3" role="alert">
<div th:each="message : ${messages}">[[${message}]]</div>
<button type="button" class="close btn-sm" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
Display List of Files
Firstly, we need to create FileInfo
model which has fields: name
& url
.
model/FileInfo.java
package com.bezkoder.spring.thymeleaf.file.upload.model;
public class FileInfo {
private String name;
private String url;
public FileInfo(String name, String url) {
this.name = name;
this.url = url;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getUrl() {
return this.url;
}
public void setUrl(String url) {
this.url = url;
}
}
In the Controller, we will return List of FileInfo
objects as model Attribute.
@GetMapping
and @PostMapping
annotation is for mapping HTTP GET & POST requests onto specific handler methods:
- GET /files:
getListFiles()
– return files.html template - GET /files/[filename]:
getFile()
– download a File byfilename
FileController.java
import com.bezkoder.spring.thymeleaf.file.upload.model.FileInfo;
import com.bezkoder.spring.thymeleaf.file.upload.service.FilesStorageService;
@Controller
public class FileController {
@Autowired
FilesStorageService storageService;
// ...
@GetMapping("/")
public String homepage() {
return "redirect:/files";
}
@GetMapping("/files")
public String getListFiles(Model model) {
List<FileInfo> fileInfos = storageService.loadAll().map(path -> {
String filename = path.getFileName().toString();
String url = MvcUriComponentsBuilder
.fromMethodName(FileController.class, "getFile", path.getFileName().toString()).build().toString();
return new FileInfo(filename, url);
}).collect(Collectors.toList());
model.addAttribute("files", fileInfos);
return "files";
}
@GetMapping("/files/{filename:.+}")
public ResponseEntity<Resource> getFile(@PathVariable String filename) {
Resource file = storageService.load(filename);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + file.getFilename() + "\"").body(file);
}
}
files.html
<div class="container-fluid" style="max-width: 600px; margin: 0 auto;">
<h2 class="text-center">List of Files</h2>
<div th:if="${files.size() > 0}">
<table class="table table-hover">
<thead class="thead-light">
<tr>
<th scope="col">File Name</th>
<th scope="col">Link</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody>
<tr th:each="file : ${files}">
<td>[[${file.name}]]</td>
<td><a th:href="@{${file.url}}">Download</a></td>
<td>
<a th:href="@{'/files/delete/' + ${file.name}}" th:fileName="${file.name}" id="btnDelete"
title="Delete this file" class="fa-regular fa-trash-can icon-dark btn-delete"></a>
</td>
</tr>
</tbody>
</table>
</div>
<div th:unless="${files.size() > 0}">
<span>No files found!</span>
</div>
</div>
For deleting file, kindly visit following tutorial:
Spring Boot Delete File example with Thymeleaf
Configure Multipart File for Servlet
Let’s define the maximum file size that can be uploaded in application.properties as following:
spring.servlet.multipart.max-file-size=1MB
spring.servlet.multipart.max-request-size=1MB
– spring.servlet.multipart.max-file-size
: max file size for each request.
– spring.servlet.multipart.max-request-size
: max request size for a multipart/form-data.
Handle File Upload Exception
This is where we handle the case in that a request exceeds Max Upload Size. The system will throw MaxUploadSizeExceededException
and we’re gonna use @ControllerAdvice
with @ExceptionHandler
annotation for handling the exceptions.
exception/FileUploadExceptionAdvice.java
package com.bezkoder.spring.thymeleaf.file.upload.exception;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import java.util.ArrayList;
import java.util.List;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
@ControllerAdvice
public class FileUploadExceptionAdvice {
@ExceptionHandler(MaxUploadSizeExceededException.class)
public String handleMaxSizeException(Model model, MaxUploadSizeExceededException e) {
List<String> messages = new ArrayList<>();
messages.add("One of selected files is too large!");
model.addAttribute("messages", messages);
return "upload_form";
}
}
Initialize Storage
We need to run init()
method of FilesStorageService
(and also deleteAll()
if necessary). So open ThymeleafMultipleFileUploadApplication.java and implement CommandLineRunner
for run()
method like this:
package com.bezkoder.spring.thymeleaf.file.upload;
import javax.annotation.Resource;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import com.bezkoder.spring.thymeleaf.file.upload.service.FilesStorageService;
@SpringBootApplication
public class ThymeleafMultipleFileUploadApplication implements CommandLineRunner {
@Resource
FilesStorageService storageService;
public static void main(String[] args) {
SpringApplication.run(ThymeleafMultipleFileUploadApplication.class, args);
}
@Override
public void run(String... arg) throws Exception {
// storageService.deleteAll();
storageService.init();
}
}
Run the Spring Boot Multiple File Upload with Thymeleaf example
Run Spring Boot application with command: mvn spring-boot:run
.
Source Code
You can find the complete source code for this tutorial on Github.
For deleting file:
Spring Boot Delete File example with Thymeleaf
Conclusion
Today we’ve learned how to create Spring Boot Thymeleaf Multiple File Upload Application with multipart files and get files’ information with static folder.
Or fullstack with frontend:
– Angular + Spring Boot: File upload example
– React + Spring Boot: File upload example
You can also know way to upload an Excel/CSV file and store the content in MySQL database with the post:
– Spring Boot: Upload/Import Excel file data into MySQL Database
– Spring Boot: Upload/Import CSV file data into MySQL Database
If you want to store files in database like this:
You can find instruction at:
Spring Boot Upload/Download File to/from Database example
Happy Learning! See you again.
Further Reading
– Spring Boot upload multiple files Rest API
– Spring Boot Thymeleaf CRUD example
– Spring Boot Thymeleaf Pagination and Sorting example
Exception Handling:
– Spring Boot @ControllerAdvice & @ExceptionHandler example
– @RestControllerAdvice example in Spring Boot
Unit Test:
– Spring Boot Unit Test for JPA Repository
– Spring Boot Unit Test for Rest Controller
Deployment:
– Deploy Spring Boot App on AWS – Elastic Beanstalk
Associations:
– Spring Boot One To One example with JPA, Hibernate
– Spring Boot One To Many example with JPA, Hibernate
– Spring Boot Many to Many example with JPA, Hibernate