Uploading Files with Spring Boot

Introduction

Uploading files to a website isn't an uncommon task, but it also isn't very straightforward to achieve. Some use-cases for why you'd want to upload a file to a website includes services that offer online file conversions and photo sharing websites. In certain applications, we might even want to send a file to another user, etc.

Spring provides a MultipartFile interface to handle HTTP multi-part requests for uploading files. Multipart-file requests break large files into smaller chunks which makes it efficient for file uploads. More information about it can be found here.

Project Setup

To demonstrate file uploading, we'll be building a typical Spring MVC application which consists of a Controller, a Service for backend processing, and Thymeleaf for view rendering.

The simplest way to start with a skeleton Spring Boot project, as always, is using Spring Initializr. Select your preferred version of Spring Boot and add the Web and Thymeleaf dependencies:

spring_file_upload_create_project

After this, generate it as a Maven project and you're all set!

Building the Application

Service Class

Let's start by building the Service layer first. We will name it as FileService.java:

@Service
public class FileService {

    @Value("${app.upload.dir:${user.home}}")
    public String uploadDir;

    public void uploadFile(MultipartFile file) {

        try {
            Path copyLocation = Paths
                .get(uploadDir + File.separator + StringUtils.cleanPath(file.getOriginalFilename()));
            Files.copy(file.getInputStream(), copyLocation, StandardCopyOption.REPLACE_EXISTING);
        } catch (Exception e) {
            e.printStackTrace();
            throw new FileStorageException("Could not store file " + file.getOriginalFilename()
                + ". Please try again!");
        }
    }
}

Let's break it down line by line:

  • @Service is a specialization of the @Component annotation. It tells Spring that this is a service class. Typically all the business logic is written in this layer.
  • We then have a variable uploadDir, which we will be using to store the path of the directory we want our file to be uploaded to. It is annotated with @Value, which means its value can be set by the application.properties file by the app.upload.dir key. In case this key is not defined, the default value is user.home - which is there in an environment variable of every OS.
  • Then we have a public method uploadFile which takes in a MultipartFile as an argument.
  • We then created the full Path of the file by using the Paths class provided by Java. StringUtils.cleanPath is used to clean the path and we simply append uploadDir to it using a File.separator. Always use utility methods to handle paths in code because it will automatically handle different OS implementations. For example, in Windows, the file separator is \ while in Linux it's /.
  • Then we copy the file to the location using Files.copy. The REPLACE_EXISTING copy option will override any file with the same name there.
  • If there is an Exception in this whole process we captured it and threw a custom FileStorageException exception.

Custom Exception

We wrote a custom FileStorageException for any exception during the file upload process. It's a simple class that extends RuntimeException:

public class FileStorageException extends RuntimeException {

    private static final long serialVersionUID = 1L;
    private String msg;

    public FileStorageException(String msg) {
        this.msg = msg;
    }

    public String getMsg() {
        return msg;
    }
}

To be able to use the exception in the way that we did, Spring needs to know how to deal with it if it's encountered. For that, we've created an AppExceptionHandler which is annotated with @ControllerAdvice and has an @ExceptionHandler defined for FileStorageException:

@ControllerAdvice
public class AppExceptionHandler {

    @ExceptionHandler(FileStorageException.class)
    public ModelAndView handleException(FileStorageException exception, RedirectAttributes redirectAttributes) {

        ModelAndView mav = new ModelAndView();
        mav.addObject("message", exception.getMsg());
        mav.setViewName("error");
        return mav;
    }
}

In the handleException method we simply returned the ModelAndView object that will return the error message set in a view of error, which is just a Thymeleaf template named error.html:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <title>ERROR</title>
</head>
<body>
  <h1>Error!!!</h1>
  <div th:if="${message}">
    <h2 th:text="${message}"/>
  </div>
</body>
</html>

If you'd like to read more about exceptions in Java and Spring, we've covered it in detail in the following articles:

Controller and Frontend

Let's now create a simple FileController class that will use the FileService to handle file uploading:

@Controller
public class FileController {

    @Autowired
    FileService fileService;

    @GetMapping("/")
    public String index() {
        return "upload";
    }

    @PostMapping("/uploadFile")
    public String uploadFile(@RequestParam("file") MultipartFile file, RedirectAttributes redirectAttributes) {

        fileService.uploadFile(file);

        redirectAttributes.addFlashAttribute("message",
            "You successfully uploaded " + file.getOriginalFilename() + "!");

        return "redirect:/";
    }
}

Let's break it down line by line:

  • @Controller annotation is also a specialization of @Component annotation. It makes a class accept the HTTP request and respond accordingly. It also takes care of the various conversion of request payload to an internal data structure.
  • Next we @Autowired the FileService bean so that we can use its uploadFile method.
  • Then we have a simple GetMapping at / which will simply return String upload. Being a controller class Spring will search for upload.html and serve it to the browser.
  • Next, we have a PostMapping of /uploadFile, which have a RequestParam of MultipartFile which is an object that has our file and its metadata details.
  • We then used the FileService uploadFile method to upload the file. RedirectAttributes is a specialization of Spring Model interface that is use to select attributes for a redirect scenario.
  • If the above operation is successful, we set the success message in redirectAttributes and redirect to the same page.

Now let's make another Thymeleaf template,upload.html:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
  <h1>Spring Boot File Upload Example</h1>
  <hr/>
  <h4>Upload Single File:</h4>
  <form method="POST" th:action="@{/uploadFile}" enctype="multipart/form-data">
    <input type="file" name="file"/> <br/><br/>
    <button type="submit">Submit</button>
  </form>
  <hr/>
  <div th:if="${message}">
    <h2 th:text="${message}"/>
  </div>
</body>
</html>

Above, we have a simple form that maps to the /uploadFile URL. Notice that it's enctype is multipart/form-data and input type as file. At the bottom it has a message div to display the success message.

Our main class is a typical Spring Boot main class:

@SpringBootApplication
public class FileIoApplication {

    public static void main(String[] args) {
        SpringApplication.run(FileIoApplication.class, args);
    }
}

Let's run our application and navigate to http://localhost:8080:

spring_file_upload_upload_single_file_preview

Choose a file and upload, you should see something like:

spring_file_upload_upload_single_file_result

Uploading Multiple Files

Similarly, we can write code for uploading multiple files. Add the following mapping in FileController.java:

@PostMapping("/uploadFiles")
public String uploadFiles(@RequestParam("files") MultipartFile[] files, RedirectAttributes redirectAttributes) {

    Arrays.asList(files)
        .stream()
        .forEach(file -> fileService.uploadFile(file));

    redirectAttributes.addFlashAttribute("message",
        "You successfully uploaded all files!");

    return "redirect:/";
}

As you can see, the /uploadFiles mapping is similar to the previous one, except it has a MultipartFile[] as an argument. We used the Java 8 Stream API to upload each file in the array.

Just like before, if the above operation is successful, we set the success message in redirectAttributes and redirect to the same page.

Now, we just need to update the code in the template upload.html to handle this:

<h4>Upload Multiple Files:</h4>
<form method="POST" th:action="@{/uploadFiles}" enctype="multipart/form-data">
  <input type="file" name="files" multiple/> <br/><br/>
  <button type="submit">Submit</button>
</form>

<hr/>

The only thing different from the previous HTML is that the mapping is to the /uploadFiles endpoint and the input has a multiple attribute, allowing you to select more than one file. Also since the @RequestParam is files, we have to use the same name in HTML.

Let's run our application again and navigate to http://localhost:8080:

spring_file_upload_upload_multiple_file_preview

Choosing the second option now allows us to select more than one file from our file system and upload them all.

spring_file_upload_upload_multiple_file_result

Limiting Filesize

You can tune the file upload limits by using spring.servlet.multipart.max-file-size and spring.servlet.multipart.max-request-size in application.properties:

spring.servlet.multipart.max-file-size = 5MB
spring.servlet.multipart.max-request-size = 5MB

You can set the limits in KB, MB, GB, etc.

The default value for spring.servlet.multipart.max-file-size is 1MB and the default for spring.servlet.multipart.max-request-size is 10MB. Increasing the limit for max-file-size is probably a good idea since the default is very low, but be careful not to set it too high, which could overload your server.

Conclusion

In this article, we've covered how to upload a single file and multiple file in a Spring Boot application. We used Spring's MultipartFile interface to capture file in the HTTP request and Thymeleaf templates as our rendering engine.

As always, the code for the examples used in this article can be found on GitHub.