RESTFul File Upload Using Spring Boot

In any application development, file upload is a feature that a developer cannot discard. In this post, I am going to share how to implement file upload in RESTFul Spring Boot application.

How do I upload files to Spring Boot REST?

Before getting into the main content, I would like to share that you can find other posts in the field of Spring Boot here.

Kindly note that, in the code sample that I will be sharing here, I have used Lombok annotations to generate getters and setters, and constructors.

Spring Boot Rest APIs for uploading Files

RESTFul Controller for File Upload in Spring Boot

First, let us create resource mappings/endpoints that we need typically in file uploads.

The first endpoint is for uploading the file. The file upload method takes in a parameter of MultipartFile.

The second endpoint is for getting all the files inside the directory.

The third endpoint is for getting a file by file name as the search keyword.

The last endpoint is for deleting a by based on the input parameter (path of the file) submitted by the consumer.

package com.bhutanio.learningspringboot;

import lombok.AllArgsConstructor;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;

import java.util.List;
import java.util.stream.Collectors;

@RestController
@AllArgsConstructor
@CrossOrigin
@RequestMapping(value = "/files")
public class FileUploadController {
    private final FileUploadService fileUploadService;

    @PostMapping("/upload")
    public ResponseEntity<ResponseMessage> uploadFile(@RequestParam("file") MultipartFile file) {
        String message = "";
        try {
            fileUploadService.save(file);

            message = "Uploaded the file successfully: " + file.getOriginalFilename();
            return ResponseEntity.status( HttpStatus.OK)
                    .body(new ResponseMessage(message));
        } catch (Exception e) {
            message = "Could not upload the file: " + file.getOriginalFilename() + "!";
            return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED)
                    .body(new ResponseMessage(message));
        }
    }

    @GetMapping("/getfiles")
    public ResponseEntity<List<FileInfo>> getListFiles() {
        List<FileInfo> fileInfos = fileUploadService.loadAll().map(path -> {
            String filename = path.getFileName().toString();
            String url = MvcUriComponentsBuilder
                    .fromMethodName(FileUploadController.class, "getFile", path.getFileName().toString()).build().toString();
            return new FileInfo(filename, url);
        }).collect( Collectors.toList());
        return ResponseEntity.status(HttpStatus.OK).body(fileInfos);
    }

    @GetMapping("/files/{filename:.+}")
    @ResponseBody
    public ResponseEntity<Resource> getFile(@PathVariable String filename) {
        Resource file = fileUploadService.load(filename);
        return ResponseEntity.ok()
                .header( HttpHeaders
                        .CONTENT_DISPOSITION, "attachment; filename=\"" + file.getFilename() + "\"").body(file);
    }

    @PostMapping("/delete")
    public ResponseEntity<ResponseMessage> deleteFile(@RequestParam("path") String filename) {
        String message = "";
        try {
            fileUploadService.deleteByPath(filename);
            message = "Deleted the file successfully: " + filename;
            return ResponseEntity.status( HttpStatus.OK).body(new ResponseMessage(message));
        } catch (Exception e) {
            message = "Could not delete the file: " + filename + "!";
            return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).body(new ResponseMessage(message));
        }
    }
}

Create Service Interface

Let us create services for the endpoints we created above. Here is the interface that we’ll be using in the implementation of the service.

package com.bhutanio.learningspringboot;

import org.springframework.core.io.Resource;
import org.springframework.web.multipart.MultipartFile;

import java.nio.file.Path;
import java.util.stream.Stream;

public interface FileUploadService {
    public void init();

    public void save(MultipartFile file);

    public Resource load(String filename);

    public void deleteAll();

    public void deleteByPath(String path);

    public Stream<Path> loadAll();
}

Create Service Implementation

Here is the class that implements the service interface that we created above. We will be annotating this class with @Service.

package com.bhutanio.learningspringboot;

import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.stereotype.Service;
import org.springframework.util.FileSystemUtils;
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.Paths;
import java.util.stream.Stream;

@Service
public class FilesStorageServiceImpl implements FileUploadService{
    private final Path root = Paths.get("C:/storage/");

    @Override
    public void init() {
        try {
            if(!Files.exists( root )) Files.createDirectory(root);
        } catch (IOException e) {
            throw new RuntimeException("Could not initialize folder for upload!");
        }
    }

    @Override
    public void save(MultipartFile file) {

        try {
            Files.copy(file.getInputStream(), this.root.resolve(file.getOriginalFilename()));
        } catch (Exception e) {
            throw new RuntimeException("Could not store the file. Error: " + e.getMessage());
        }
    }

    @Override
    public Resource load(String filename) {
        try {
            Path file = root.resolve(filename);
            Resource resource = new UrlResource(file.toUri());

            if (resource.exists() || resource.isReadable()) {
                return resource;
            } else {
                throw new RuntimeException("Could not read the file!");
            }
        } catch (MalformedURLException e) {
            throw new RuntimeException("Error: " + e.getMessage());
        }
    }

    @Override
    public void deleteAll() {
        FileSystemUtils.deleteRecursively(root.toFile());
    }

    @Override
    public void deleteByPath(String filename) {
        try{
            Path file = root.resolve(filename);
            Resource resource = new UrlResource(file.toUri());

            if (resource.exists() || resource.isReadable()) {
                Files.delete( ( file ));
            } else {
                throw new RuntimeException("Could not read the file!");
            }}catch (Exception ex){
        }
    }

    @Override
    public Stream<Path> loadAll() {
        try {
            return Files.walk(this.root, 1).filter(path -> !path.equals(this.root)).map(this.root::relativize);
        } catch (IOException e) {
            throw new RuntimeException("Could not load the files!");
        }
    }
}

NOTE: In the code above, I have set C:/storage/ as my root folder for file uploads. You can change as per your requirements.

Creating Models

In the sample codes I have shown above, I have used two models – FileInfo and ResponseMessage.

As stated earlier, in these two models you will not see getters, setters and constructors. This is because I have used Lombok annotations for creating them.

Model for file information

package com.bhutanio.learningspringboot;

import lombok.AllArgsConstructor;
import lombok.Data;

@AllArgsConstructor
@Data
public class FileInfo {
    private String name;
    private String url;
}

Model for response messages

package com.bhutanio.learningspringboot;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class ResponseMessage {
    private Integer status;
    private String text;
    private String responseText;

    public ResponseMessage(String text) {
        this.text = text;
    }
}

Initiating File Upload Service on project startup

To initiate the file upload service on the project startup, we will inject FileUploadService with @Resource annotation.

The main class implements CommandLineRunner.

package com.bhutanio.learningspringboot;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import javax.annotation.Resource;

@SpringBootApplication
public class LearningSpringBootApplication implements CommandLineRunner {

    @Resource
    FileUploadService fileUploadService;

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

    @Override
    public void run(String... arg) throws Exception {
        fileUploadService.deleteAll();
        fileUploadService.init();
    }

}

On the project startup, deleteAll() method will be executed deleting all the files in the upload directory because of this line:

fileUploadService.deleteAll();

If you do not want this to happen, you can discard this line of code.

The following line of code will assist in initializing the file upload – creates the folder if it is not found or return a message of failure if it cannot be created. Here is the code snippet that performs this:

 public void init() {
        try {
            if(!Files.exists( root )) Files.createDirectory(root);
        } catch (IOException e) {
            throw new RuntimeException("Could not initialize folder for upload!");
        }
    }
RESTFul file upload with progress bar
RESTFul file upload with progress bar

The next post will be RESTFul file upload with progress bar in Angular framework.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *