REST API File Uploads with Spring Boot

3 minute read

This page will explore how we can use a REST API to upload files in Spring Boot, and how we can test the REST API using Postman.

How to design a REST API to upload files

In order to upload files to a server using a REST API, data is sent via the “multipart/form-data” content type.

  • Note: If you want to know more about all the media types check out here

What is Multipart form data?

The official specs can be found here

It is “a way of returning a set of values as the result of a user filling out a form.”

Typically HTML forms have file uploads - this internet media type was introduced to solve the problem of sending files.

An example of a multi-part form request looks like:

POST /upload HTTP/1.1
User-Agent: PostmanRuntime/7.24.0
Accept: */*
Cache-Control: no-cache
Postman-Token: f3895842-da7e-4904-a4e5-cfb4b7ad10b5
Host: localhost:8080
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Type: multipart/form-data; boundary=--------------------------958134059024028642431303
Cookie: JSESSIONID=B1CDCF89D088D9834323E88AD612A249
Content-Length: 544
----------------------------958134059024028642431303
Content-Disposition: form-data; name="file"; filename="file.log"

<file.log>
----------------------------958134059024028642431303
Content-Disposition: form-data; name="randomkey"

randomvalue
----------------------------958134059024028642431303--

What makes up multipart form request?

  1. Content-disposition:

    Each part of the form will contain a “content-disposition” header, along with the name of the form field:

     Content-Disposition: form-data; name="file"; filename="file.log"
    

    In the snippet above, the form field was named “file” and we uploaded the file “file.log”

  2. Boundary

    If you look at the request body the “boundary” is repeated a few times:

     boundary=--------------------------958134059024028642431303
    

Boundaries are used to separate file information or field information

For example, here’s a random example of a boundary:

Content-Type: multipart/form-data; **boundary="my boundary"**

**--my boundary**
Content-Disposition: form-data; name="lorem"

ipsum
**--my boundary**
Content-Disposition: form-data; name="dolor"

amet
**--my boundary--**

Spring Boot Implementation

Let’s actually start to build our REST API to upload files in Spring Boot

Setup

To get started create a Spring Boot application and add the spring-boot-starter-web to the dependencies in the pom.xml:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

Designing the endpoint for multipart requests:

Next we will expose a POST request endpoint that will accept the file:

// FileUploadController.java

@RestController
public class FileUploadController {

    private static final Logger log = LoggerFactory.getLogger(FileUploadController.class);
    private final FileSystemStorageService storageService;

    @Autowired
    public FileUploadController(FileSystemStorageService storageService) {
        this.storageService = storageService;
    }

    @PostMapping("/upload")
    public Map<String, String> handleFileUpload(@RequestParam("file") MultipartFile file) {

        storageService.store(file);
        log.info("Successfully stored file {}", file.getOriginalFilename());

        HashMap<String, String> result = new HashMap<>();
        result.put("status", "success");
        result.put("file", file.getOriginalFilename());

        return result;
    }

}

There are a few things to note in this class:

  • FileSystemStorageService: This is a service we made that handles the actual processing of the MultipartFile
  • @RestController: This annotation registers this class with Spring MVC, so the MVC framework will look for endpoints in this class
  • @PostMapping: This ties our method to the POST HTTP action for the “/upload” resource
  • @RequestParam: This says that our API expects that the body contains the key “file” with a value of MultipartFile
  • MultipartFile: This is a class that represents an uploaded file in multipart requests

Designing a service to handle multi-part request

Above we made a generic service that handles the processing of the MultipartFile.

In this example, the file will be saved to the file system, but in reality we can make it do a variety of things. One option would be to upload the file to a cloud provider such as Amazon S3.

@Service
public class FileSystemStorageService {

    private final Path rootLocation;

    @Autowired
    public FileSystemStorageService(FileSystemStorageProperties storageProperties) {
        this.rootLocation = Paths.get(storageProperties.getLocation());
    }

    public void store(MultipartFile file) {
        try {
            String filename = StringUtils.cleanPath(file.getOriginalFilename());
            if (file.isEmpty()) {
                throw new FileStorageException("File is empty:" + file.getOriginalFilename());
            }

            InputStream inputStream = file.getInputStream();
            Files.copy(inputStream, this.rootLocation.resolve(filename), StandardCopyOption.REPLACE_EXISTING);

        } catch (IOException e) {
            throw new FileStorageException("Failed to store file" + file.getOriginalFilename());
        }
    }

    public void init() {
        try {
            Files.createDirectory(rootLocation);
        } catch (IOException e) {
            throw new FileStorageException("Could not initialize storage");
        }
    }

}

/

Testing file upload in Postman

Now that we’ve made our REST API, we can test it out by sending requests in Postman.

To start up the REST API: $ sh mvnw spring-boot:run

In Postman, we send multi-part files using the choose the “form-data” option in the “Body” tab:

REST%20API%20File%20Uploads%20with%20Spring%20Boot/Untitled.png

This allows us to choose a file to upload as part of our request

Checking the response

If you open the Postman console you should see the response look something like:

REST%20API%20File%20Uploads%20with%20Spring%20Boot/Untitled%201.png

We can also check out the raw request/response:

REST%20API%20File%20Uploads%20with%20Spring%20Boot/Untitled%202.png

Notice the Content-Disposition header and the boundary like we talked about earlier.

Updated: