5 min read

File upload with Spring Boot

November 13, 2020

File Upload is not a trivial concept in web-services. However, It is useful in some cases. In this post we will see how we can upload files using Spring MVC features in Spring Boot.

What is a multipart request?

There are three types of payloads in POST request.

  1. The Request Body is a bunch of parameters which are url-encoded
  2. The Request Body is RAW/Binary content. (Example JSON)
  3. The Request Body is a mixture of parameters and RAW content.(Multipart Form Data)

For this post, I’ll be concentrating more towards the 3rd type. A post request will be called multipart request if its body content is split using a specific boundary or delimiter. These delimiter will mark where a single parameter start and end.

For example Take a look at this below request. Multipart Request

If this form was submitted, then the server would receive the HTTP request in the form below.

POST /v3/ed8d9ae2-7d0d-4411-8b8c-66106d8a2721 HTTP/1.1
Host: run.mocky.io
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
Cache-Control: no-cache
Postman-Token: 55480fab-4fc3-26e2-fde0-19cc88f8d73e

------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="hello"

world
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="hotel"

trivago
------WebKitFormBoundary7MA4YWxkTrZu0gW--

Now try to fit the exact values in the request and the above screen shot. The first thing to give attention here is the Content-Type header. It marks the request as multipart and the boundary between each parameter is ----WebKitFormBoundary7MA4YWxkTrZu0gW. When the server receives this information, it can easily split the body of the request and will get the name of each parameter and their appropriate values.

With this setup, If one of the parameter is a file itself, then we can simply encode it within a boundary. If I add a file parameter to the above request, then the request body would look like this.

POST /v3/ed8d9ae2-7d0d-4411-8b8c-66106d8a2721 HTTP/1.1
Host: run.mocky.io
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
Cache-Control: no-cache
Postman-Token: e139ea1a-1f88-ae45-72c6-867ab4a12209

------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="hello"

world
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="hotel"

trivago
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="myfile"; filename="hello-world.txt"
Content-Type: text/plain

Hello world from a file

------WebKitFormBoundary7MA4YWxkTrZu0gW-- 

Notice how there is a new Part in the request along wiht the content of the file added to the body. With this knowledge let’s write some code.

Uploading Single file

Let’s say we have a parameter in our request that we want to use in our controller method. For example, a request contains a parameter called count and it is an integer. So the controller method parameter will be @RequestParam("count") Integer count. In this example, Integer is the appropriate type for storing the parameter count.

Similarly, Spring MVC provides a MultipartFile to hold the content of the file. So here is my simple example that takes file as the parameter.

    @RequestMapping(value = "/file-upload", method = RequestMethod.POST)
    @ResponseBody
    public String uploadFile(@RequestParam("myFile") MultipartFile multipartFile) throws IOException {
        multipartFile.transferTo(new File("C:\\data\\test\\" + multipartFile.getOriginalFilename()));
        return "success";
    }

In this example, the parameter name itself is myFile. Also, I am writing the file content to my local disk. It is up to you what you want to do with the MultipartFile object. These are the methods available part of multipart file if you are interested.

MultipartFile class methods

Uploading two files

This is easy. Just add another parameter with different @RequestParam.

    @RequestMapping(value = "/two-file-upload", method = RequestMethod.POST)
    @ResponseBody
    public String uploadTwoFile(@RequestParam("myFile") MultipartFile multipartFile, 
                                @RequestParam("myOtherFile") MultipartFile otherMultipartFile) throws IOException {
        multipartFile.transferTo(new File("C:\\data\\test\\" + multipartFile.getOriginalFilename()));
        otherMultipartFile.transferTo(new File("C:\\data\\test\\" + otherMultipartFile.getOriginalFilename()));
        return "success";
    }

Uploading multiple files

Spring MVC processes same parameter with different values into an array or collection. This is true for multipart file parameters as well. If you have to upload countably many number of files without hard coding each file parameter name, this is the way to go.

    @RequestMapping(value = "/multiple-file-upload", method = RequestMethod.POST)
    @ResponseBody
    public String uploadMultipleFiles(@RequestParam("myFiles") MultipartFile[] multipartFiles) throws IOException {
        for (MultipartFile multipartFile : multipartFiles) {
            multipartFile.transferTo(new File("C:\\data\\test\\" + multipartFile.getOriginalFilename()));
        }
        return "success";
    }

Things to consider when writing an upload handler

Uploads are resource heavy tasks. In terms of storage and network bandwidth, these operations are costly. So there are few parameters to consider while implementing any of the above snippets in to your project.

Limit multipart file size

Yes. You can limit the maximum file size that can be uploaded to your system. For this we don’t even have to write any piece of code. The following property will let the client upload files of size below 5KB.

spring.servlet.multipart.max-file-size=5KB

Any request with files above 5KB will get a 500 internal server error with an exception similar to below.

2020-11-14 00:31:14.227 ERROR 16596 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.web.multipart.MaxUploadSizeExceededException: Maximum upload size exceeded; nested exception is java.lang.IllegalStateException: org.apache.tomcat.util.http.fileupload.impl.FileSizeLimitExceededException: The field myFiles exceeds its maximum permitted size of 5120 bytes.] with root cause

org.apache.tomcat.util.http.fileupload.impl.FileSizeLimitExceededException: The field myFiles exceeds its maximum permitted size of 5120 bytes.
	at org.apache.tomcat.util.http.fileupload.impl.FileItemStreamImpl$1.raiseError(FileItemStreamImpl.java:114) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
	at org.apache.tomcat.util.http.fileupload.util.LimitedInputStream.checkLimit(LimitedInputStream.java:76) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
	at org.apache.tomcat.util.http.fileupload.util.LimitedInputStream.read(LimitedInputStream.java:135) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
	at java.io.FilterInputStream.read(FilterInputStream.java:107) ~[na:1.8.0_221]

Note that the default value for this configuration is 1MB

Consider using lazy parsing

Spring MVC parses all multipart requests immediately. If the parsed files may or may not be used further, then it is better if we can defer the parsing only if the file is actually being used. This way we can save CPU usage. For this behaviour we need to add the following configuration to the application properties.

spring.servlet.multipart.resolve-lazily=true

The source code for all of the above examples are available in the github link below. Please feel free to drop a comment below if you want to add something to this topic or if you have any questions.

About the author
Raja Anbazhagan

I'm a Software Engineer with over 7 years of experience in working with Enterprise Java applications. Lately, I am focused on cloud-based Java applications and serverless technologies. I spend most of my spare time on stackoverflow and this blog.