Skip to main content

How to Upload File in Spring Boot

Spring Spring-Boot File-Upload Thymeleaf
Author
Harpal Singh
Software Engineer
Table of Contents

1. Introduction

In this quick tutorial, we’ll learn how to upload a file using Spring Boot.

Let’s see how to do it through a form submission.

2. Why File Uploads Are Important?

Uploading files is a very common use case for modern web applications, for example, attaching documents in forms or uploading profile pictures. While simple to understand, handling files correctly comes with some challenges. For instance, exposing an application with no constraints on the file size users can upload can overwhelm our application or consume all available storage. Therefore, before accepting any file, we need to validate its type and content and handle failures gracefully.

So, in our application, we’ll use options offered by Spring Boot to make it robust enforcing a limit on the file size, and providing feedback so the user understands what’s happening.

3. Upload Flow

The following sequence diagram visually shows the interactions between the User, Browser, and our Spring Boot application:

sequenceDiagram participant User as User participant Browser as Browser participant Controller as UploadController participant Filesystem as File System User->>Browser: Selects file and submits form Browser->>Controller: POST /upload (multipart/form-data) Controller->>Controller: Check if file is empty alt File is valid Controller->>Filesystem: Save file bytes to disk Controller-->>Browser: Redirect to / with success message else File is invalid Controller-->>Browser: Redirect to / with error message end Browser->>User: Display upload status message


4. Maven Configuration

Let’s start by including our dependencies. Make sure to get the latest versions of spring-boot-starter-web and spring-boot-starter-thymeleaf.

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>3.4.2</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
        <version>3.4.2</version>
    </dependency>
</dependencies>

For a new project, it’s best to start from the Spring Initializr and include these dependencies:

  • Spring Web: MVC framework & embedded Tomcat
  • Thymeleaf: A modern server-side Java template engine for both web and standalone environments.
Tip: Use Spring Boot DevTools for automatic restarts during development.

5. Implementing File Upload

Let’s start implementing our application.

5.1 Upload Form in Thymeleaf

First, we’ll create a Thymeleaf template that includes a basic form with a file input and a button to submit the form to our endpoint.

We need to create a file-upload.html file in a folder called templates within the application’s resources folder:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/mini.css/3.0.1/mini-default.min.css">
</head>
<body>
    <div class="container">
        <header>
            <h2>File Upload</h2>
        </header>
        <main>
            <form action="#" th:action="@{/upload}" method="post" enctype="multipart/form-data">
                <div class="input-group vertical">
                    <input type="file" name="file" required>
                    <button type="submit">Upload</button>
                </div>
            </form>
            <div th:if="${message}" class="alert"
                 th:classappend="${message.contains('Success') ? 'alert-success' : 'alert-error'}"
                 th:text="${message}"></div>
        </main>
    </div>
</body>
</html>
A very common error is forgetting to add the enctype="multipart/form-data" attribute. Without it, the browser will not send the file as multipart data, and the upload won’t work as expected.

After the form, we added a placeholder to display messages.

In our template, we are using mini.css so that we don’t have to deal with the application’s styling but still avoid the browser’s default look.

That’s all for the front-end side.

5.2. Storage Configuration

Next, we need to decide where to save the files our users upload. Let’s add a files.upload-dir property in the application.properties file:

server.port=8090
files.upload-dir=./uploadedfiles

This will be the location where we save the files.

5.3 File Upload Controller

Now, we’ll implement a controller named FileUploadController that exposes an endpoint to serve the template we created to the browser:

@Controller
public class FileUploadController {

    @GetMapping("/")
    public String fileUpload() {
        return "file-upload";
    }

}

We just need to make sure that the string we return matches the name of the template we created previously.

Next, in the same controller, we’ll create a POST /upload endpoint to handle file upload requests. This endpoint will use a MultipartFile as a request parameter to get the file. MultipartFile is an interface that extends InputStreamSource:

@Value("${files.upload-dir}")
private String uploadDir;

@PostMapping("/upload")
public String handleUpload(
    @RequestParam("file") MultipartFile file,
    RedirectAttributes redirectAttributes
) {
    if (file.isEmpty()) {
        redirectAttributes.addFlashAttribute("message", "Please select a file");
        return "redirect:/";
    }
    try {
        String filename = StringUtils.cleanPath(file.getOriginalFilename());
        Path destination = Paths.get(uploadDir, filename);
        Files.createDirectories(destination.getParent());
        file.transferTo(destination);
        redirectAttributes.addFlashAttribute("message", "Success: " + filename + " (" + file.getSize() + " bytes)");
        return "redirect:/";
    } catch (IOException e) {
        redirectAttributes.addFlashAttribute("message", "Error: " + e.getMessage());
        return "redirect:/";
    }
}

In short, first, we get the name of the file from the input file object. Then, we create the folders required to save destination using StringUtils.cleanPath so we can prevent simple path traversal attacks. Then we save the incoming file data to destination and send a success message. In case of failure we notify the user accordingly just so he knows what is happening.

Also, in the controller, we injected the upload directory using @Value and implemented a @PostMapping method that takes the MultipartFile along with a RedirectAttributes object. The RedirectAttributes interface in Spring MVC is used to pass data during a redirect. Specifically, we used addFlashAttribute to save the message in the session temporarily, so it’s available after the redirect when the template is processed, allowing it to display the correct feedback.

StringUtils.cleanPath should not be depended upon in a security context. Other mechanisms should be used to prevent path-traversal issues.

Now, if we run the application using mvn spring-boot:run and go to localhost:8090, we should see our form and be able to upload small files.

6. Handling Large Files

We can now upload small files without any issues. But if the file size is larger than 1MB (the default limit), our application will fail, and the browser might display an error. If we want to allow users to upload larger files, we can configure a higher threshold.

6.1 Configure Maximum File Size

To configure the maximum allowed file size, we change the spring.servlet.multipart.max-file-size and spring.servlet.multipart.max-request-size properties in application.properties:

spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=11MB
Common Mistake: Request size must be greater than file size to account for form data overhead.

This is not enough on its own. We also need to configure our servlet container so it doesn’t abort a connection if the incoming request exceeds its default maximum size value before Spring’s multipart handling kicks in. Our Spring Boot application uses the embedded Tomcat by default. We can configure it to accept larger (or effectively unlimited size) POST requests so that the size checks are handled solely by Spring’s multipart configuration:

server.tomcat.max-http-form-post-size=-1
server.tomcat.max-swallow-size=-1
Security Concerns: Setting limits to -1 is generally not recommended as it allows potentially unlimited size requests, which can lead to Denial of Service (DoS) attacks. Ideally, we should limit the size of the request to a reasonable value appropriate for the application’s use case.

Now, if we try to upload files larger than 10MB, Spring MVC will prevent the upload, but the default behavior might still result in an unhandled error page (like a Whitelabel Error Page showing a 500 error related to MaxUploadSizeExceededException). If we want to customize this behavior so the interaction in the UI is smoother, we can add custom error handling.

6.2 Custom Error Handling

Behind the scenes, Spring MVC throws a MaxUploadSizeExceededException when a file exceeds the configured size limit. We can create a @ControllerAdvice to catch MultipartException globally and handle these errors gracefully. We catch MultipartException because MaxUploadSizeExceededException extends it, allowing us to handle other potential multipart-related errors as well:

@ControllerAdvice
public class FileUploadExceptionHandler {

    @ExceptionHandler(MultipartException.class)
    public String handleSizeExceeded(RedirectAttributes attrs) {
        attrs.addFlashAttribute("message",
            "File exceeds size limit!");
        return "redirect:/";
    }

}

Configuring a @ControllerAdvice provides a centralized way to handle exceptions across multiple controllers and return user-friendly error messages or redirect with appropriate feedback.

7. Tips for Production Environments

While this tutorial explains a simple file upload, there are several additional considerations for production environments not covered here.

First, we are not limiting the type of files allowed for upload. We could resolve this with a simple file extension check or implement more in-depth validation, such as checking the MIME type or scanning for viruses and malware.

In production environments where large files are expected, consider using a streaming approach to avoid loading the entire file into memory on the server. Alternatively, consider using chunked uploads from the client-side, where the file is split into smaller parts, uploaded separately, and then reassembled on the server-side.

8. Conclusion

In this article, we learned how to implement file uploads in Spring Boot using a simple HTML form, Thymeleaf, and a Spring MVC controller. We also learned how to configure limits for large files and customize the error handling process for size exceptions.

You can find the complete code on GitHub.

Related

Http Requests With Rest Client in Spring Framework 6
Spring Restclient HTTP Rest Openai
Understanding Spring's @Scheduled Cron Syntax
Spring Cron Scheduling
Testcontainers in Spring Boot Integration Tests
Spring Testcontainers Testing