Spring Boot Delete File example with Thymeleaf

In previous post, we’ve known how to upload and download files with a Spring Boot and Thymeleaf. In this tutorial, I will continue to show you way to delete file (if it exists) in Spring Boot.

Related Posts:
Spring Boot File upload REST API example
Spring Boot Thymeleaf CRUD example
Spring Boot Thymeleaf Pagination and Sorting example
Spring Boot @ControllerAdvice & @ExceptionHandler example
@RestControllerAdvice example in Spring Boot
Spring Boot Unit Test for JPA Repositiory
Spring Boot Unit Test for Rest Controller
Deploy Spring Boot App on AWS – Elastic Beanstalk

Fullstack:
Angular + Spring Boot: File upload example
React + Spring Boot: File upload example

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


Spring Boot Delete File with Thymeleaf example

Our Spring Boot Application has already features for:
– uploading File to a static folder in the Server

thymeleaf-file-upload-example

spring-boot-delete-file-thymeleaf-folder

– downloading File from server with the link
– getting list of Files’ information (file name & url)

spring-boot-delete-file-thymeleaf-example

Today we continue to implement Delete File feature:
1- User clicks on Trash icon.
2- A Confirmation dialog appears to ask user to approve DELETE operation.
3- User decides whether to delete the file or not.

spring-boot-delete-file-example-thymeleaf

Technology

  • Java 8
  • Spring Boot 2.7 (with Spring Web MVC, Thymeleaf)
  • Maven 3.6.1
  • Bootstrap 4
  • jQuery 3.6.1

Project Structure

We will add some functions to existing Spring Boot project:
Thymeleaf File Upload with Spring Boot

spring-boot-delete-file-thymeleaf-project

Let me explain it briefly.

FileInfo contains information of the uploaded file.
FilesStorageService helps us to initialize storage, save new file, load file, get list of Files’ info, DELETE files.
FileController uses FilesStorageService to handle 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.

Setup Spring Boot Delete File with Thymeleaf 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>

Or you can visit following tutorial for base project and Github source code:
Thymeleaf File Upload with Spring Boot

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 boolean delete(String filename);
  
  public void deleteAll();

  public Stream<Path> loadAll();
}

Now we create implementation of the interface and override the method: boolean delete(String filename).

service/FilesStorageServiceImpl.java

package com.bezkoder.spring.thymeleaf.file.upload.service;

import java.io.IOException;
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) {
    // ...
  }

  @Override
  public Resource load(String filename) {
    // ...
  }

  @Override
  public boolean delete(String filename) {
    try {
      Path file = root.resolve(filename);
      return Files.deleteIfExists(file);
    } catch (IOException e) {
      throw new RuntimeException("Error: " + e.getMessage());
    }
  }

  @Override
  public void deleteAll() {
    FileSystemUtils.deleteRecursively(root.toFile());
  }

  @Override
  public Stream<Path> loadAll() {
    // ...
  }
}

delete() method receives the filename parameter, converts a given that file string to a Path object using Path.resolve().

Then we use static method Files.deleteIfExists() to delete that file by its corresponding Path object.
Files.deleteIfExists() will return:

  • true if the file was deleted successfully.
  • false if the file cannot be deleted because it does not exist

Setup the Template

In src/main/resources folder, open files.html and add Bootstrap Modal with some lines of Jquery script for delete feature:

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>BezKoder - Thymeleaf File Upload/Delete 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>

  <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>
  
  <div class="modal fade text-center" id="confirmModal">
    <div class="modal-dialog">
      <div class="modal-content">
        <div class="modal-header">
          <h5 class="modal-title">Delete Confirmation</h5>
          <button type="button" class="close" data-dismiss="modal">
            <span aria-hidden="true">×</span>
          </button>
        </div>
  
        <div class="modal-body">
          <span id="confirmText"></span>
        </div>
  
        <div class="modal-footer">
          <a type="button" id="yesBtn" class="btn btn-danger">Yes</a>
          <button type="button" class="btn btn-secondary" data-dismiss="modal">No</button>
        </div>
      </div>
    </div>
  </div>
  
  <div th:replace="fragments/footer :: footer"></div>

  <script type="text/javascript">
    $(document).ready(function () {
      $(".btn-delete").on("click", function (e) {
        e.preventDefault();
        link = $(this);

        fileName = link.attr("fileName");
        $("#yesBtn").attr("href", link.attr("href"));
        $("#confirmText").html("Do you want to delete the File: \<strong\>" + fileName + "\<\/strong\>?");
        $("#confirmModal").modal();
      });
    });
  </script>

</body>

</html>

Create Controller for Delete File

In controller package, we create FileController.

controller/FileController.java

package com.bezkoder.spring.thymeleaf.file.upload.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.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

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);
  }

  @GetMapping("/files/delete/{filename:.+}")
  public String deleteFile(@PathVariable String filename, Model model, RedirectAttributes redirectAttributes) {
    try {
      boolean existed = storageService.delete(filename);

      if (existed) {
        redirectAttributes.addFlashAttribute("message", "Delete the file successfully: " + filename);
      } else {
        redirectAttributes.addFlashAttribute("message", "The file does not exist!");
      }
    } catch (Exception e) {
      redirectAttributes.addFlashAttribute("message",
          "Could not delete the file: " + filename + ". Error: " + e.getMessage());
    }

    return "redirect:/files";
  }
}

@Controller annotation is used to define a controller.
@GetMapping 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.

Run the Spring Boot Delete File 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.

Conclusion

Today we’ve learned how to create Spring Boot Delete File example with Thymeleaf Template Engine.

For REST API:
Spring Boot Delete File REST API example

Or upload multiple Files at once:
How to upload multiple files in Java Spring Boot

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:
spring-boot-upload-files-to-database-table-files

You can find instruction at:
Spring Boot Upload/Download File to/from Database example

Happy Learning! See you again.

Further Reading

Spring Boot Thymeleaf CRUD example
Spring Boot Thymeleaf Pagination and Sorting example
Spring Boot @ControllerAdvice & @ExceptionHandler example
@RestControllerAdvice example in Spring Boot
Spring Boot Unit Test for JPA Repositiory
Spring Boot Unit Test for Rest Controller
Deploy Spring Boot App on AWS – Elastic Beanstalk

Fullstack:
Angular + Spring Boot: File upload example
React + Spring Boot: File upload example

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