Spring Boot File Upload and Download REST API

As a developer you may need to work with files and there may be chances that you may need to work with uploading and downloading them. This post explain how you can do with spring boot

Spring Boot File Upload and Download REST API
file upload and download img

In this post, we will see how to upload and download files using Spring Boot as a RESTful web service.

If you want to watch video instead of reading this post, you can see my below video.

Create the project

Create the Spring Boot project using Spring Initializr web tool or using Intellij. I have already added required dependecies for this project, so you can use this link to generate the project and import it into your IDE.

Configure File Storage Location

Once the dependencies are resolved, open src/main/resources/application.properties, and add the following property to it

# The files uploaded through the API will be stored in this directory
file.upload-dir=./uploads

I gave uploads directory as my file storage location which would be under our project root path. This would directory will be created when we start our app (of course we will configure it). You can give your own path to be uploaded.

Binding Properties

Property values can be injected directly into your beans by using the @Value annoation, accessed through Spring's Environment abstraction, or be bound to structured objects through @ConfigurationProperties.

Create a package called property and inside the package create a class named FileStorageProperties.java to bind the file property.

package com.fileapi.property;

import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;

@Getter
@Setter
@ConfigurationProperties(prefix = "file")
public class FileStorageProperties {

    private String uploadDir;

}

We have used @ConfigurationProperties(prefix = "file") annotation so that it binds all the properties with prefix file to the fields in our class on application startup. If you want to create an additional file property in properties file, all you need to do is to just add a field in our class.

Enabling Configuration Properties

To make @ConfigurationProperties feature work, you need to add @EnableConfigurationProperties to any configuration class. We will add it in our main class. So open src/main/java/com/fileapi/SpringFileApiApplication.java and add the @EnableConfigurationProperties annoation to it like this:

package com.fileapi;

import com.fileapi.property.FileStorageProperties;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;

@SpringBootApplication
@EnableConfigurationProperties({
        FileStorageProperties.class
})
public class SpringFileApiApplication {

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

}

Services for File Upload and Download

Create a package service under com.fileapi and inside it create a class named FileService.java with the following code:

package com.fileapi.service;

import com.fileapi.property.FileStorageProperties;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.net.MalformedURLException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.Objects;

@Log4j2
@Service
public class FileService {

    private final Path fileStorageLocation;

    @Autowired
    public FileService(FileStorageProperties fileStorageProperties) {
        // get the path of the upload directory
        fileStorageLocation = Path.of(fileStorageProperties.getUploadDir());
        try {
            // creates directory/directories, if directory already exists, it will not throw exception
            Files.createDirectories(fileStorageLocation);
        } catch (IOException e) {
            log.error("Could not create the directory where the uploaded files will be stored.", e);
        }
    }

    public String storeFile(MultipartFile multipartFile) {
        // get filename
        String filename = StringUtils.cleanPath(Objects.requireNonNull(multipartFile.getOriginalFilename()));
        try {
            // check if the filename contains invalid characters
            /*if (filename.contains("..")) {
                throw new RuntimeException("Filename contains invalid path sequence " + filename);
            }*/
            // remove dots(.) in filename
            filename = filename.substring(0, filename.lastIndexOf("."))
                    .replace(".", "") + "." + filename.substring(filename.lastIndexOf(".") + 1);
            // convert path string to Path
            Path targetLocation = fileStorageLocation.resolve(filename);
            // copy file to the target location and replace existing file with the same name if exists
            Files.copy(multipartFile.getInputStream(), targetLocation, StandardCopyOption.REPLACE_EXISTING);
            return targetLocation.toString();
        } catch (IOException e) {
            throw new RuntimeException("Could not store file " + filename, e);
        }
    }

    public Resource getFile(String filename) {
        try {
            Path file = fileStorageLocation.resolve(filename).normalize();
            Resource resource = new UrlResource(file.toUri());
            if (resource.exists() || resource.isReadable()) {
                return resource;
            } else {
                throw new RuntimeException("Could not read file: " + filename);
            }
        } catch (MalformedURLException e) {
            throw new RuntimeException("Could not retrieve file " + filename, e);
        }
    }
}

REST APIs for File Upload and Download

Create a package controller under com.fileapi and inside it create a class named FileController.java with the following code:

package com.fileapi.controller;

import com.fileapi.service.FileService;
import lombok.extern.log4j.Log4j2;
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.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

import static org.springframework.http.MediaType.parseMediaType;

@Log4j2
@RestController
@RequestMapping("/api/v1/file")
public class FileController {

    private final FileService fileService;

    @Autowired
    public FileController(FileService fileService) {
        this.fileService = fileService;
    }

    @PostMapping("/upload")
    public ResponseEntity<?> uploadFile(@RequestParam("file") MultipartFile multipartFile) {
        return ResponseEntity.ok(fileService.storeFile(multipartFile));
    }

    @GetMapping("/download/{filename:.+}")
    public ResponseEntity<?> downloadFile(@PathVariable("filename") String filename, HttpServletRequest request) {
        Resource fileResource = fileService.getFile(filename);

        String contentType = null;

        try {
            contentType = request.getServletContext().getMimeType(fileResource.getFile().getAbsolutePath());
        } catch (IOException e) {
            log.error("Could not determine file type.");
        }

        if (contentType == null) {
            contentType = "application/octet-stream";
        }
        return ResponseEntity.ok()
                .contentType(parseMediaType(contentType))
                .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileResource.getFilename() + "\"")
                .body(fileResource);
    }
}

Testing the API via Postman

As we have created our API, let's run the application and test the APIs. Start the spring boot application and open Postman. Spring Boot runs on port 8080 by default, it can be accessed at http://localhost:8080.

Upload File

Download File

The souce code is available at github

Thanks for reading!