Upload and download the files using Spring Boot Application

By | August 17, 2020

If you are on the internet then you definitely upload or download some files such as movies, songs, or any documents like pdf, images, etc. These are very frequently used feature of any applications or software.

So, without wasting much time let’s jump into the main topic that is how to implement the upload and download feature in Spring Boot Application.

Here we will build a Restful API for uploading and downloading the files and we also develop a very simple front end using Javascript for uploading and downloading the file.

Don’t worry I am not going to use lots of javascript. It’s just five lines of code to implement our Restful API. And it is completely optional. You can skip if you are not interested or if you want to add front end code also in your application, then please download the Source code from the last section of this article.

Following are the final Application.

Upload and download the files using Spring Boot Application

So, Let’s begin

Note: Video Tutorial is available in the bottom section of this article.

Step 1: Create a Spring Boot Application.

You can create from any of the mentioned options.

  1. Create a Project using Spring Initialzr.
  2. Create a project using Spring Boot CLI.

Creating a Project from Spring Initialzr.

  1. Go to https://start.spring.io/.
  2. Enter the Group name. com.pixeltrice
  3. Give the artifact Id. upload-download-files-with-spring-boot
  4. Add Spring Web dependency.
  5. Click on Generate Project and download will starts.
  6. Once download completed, unzip the file and import it in your IDE such as Eclipse.
  7. Follow the below steps to import the file in Eclipse.

Select File -> Import -> Existing Maven Projects -> Browse -> Select the folder upload-download-files-with-spring-boot-> Finish.

Creating a Project from Spring Boot CLI.

  1. Download and install Spring Boot CLI in your system. Please follow the article Run Application from Spring Boot CLI.
  2. Open the Command Prompt and type following command.
spring init --name=upload-download-files-with-spring-boot --dependencies=web upload-download-files-with-spring-boot
Upload and download the files using Spring Boot Application

3. Press Enter and check following path for your project

Upload and download the files using Spring Boot Application
Project extracted to 'C:\Users\1302143\upload-download-files-with-spring-boot'

Great! You Succesfully completed the first Step.

Step 2: Configure the Server and File Storage Properties in the application.properties file

Here we will configure the following things.

  1. Enable the multipart file upload in Spring Boot Application.
  2. Define the maximum upload size of the file.
  3. Configure the directory on which uploaded file will be saved.

Go to application.properties file.

# Enable multipart uploads
spring.servlet.multipart.enabled=true
# Threshold after which files are written to disk.
spring.servlet.multipart.file-size-threshold=2KB
# Max file size.
spring.servlet.multipart.max-file-size=300MB
# Max Request Size
spring.servlet.multipart.max-request-size=315MB

## File Storage Properties
# All files uploaded through the REST API will be stored in this directory
file.upload-dir=C:/Users/1302143/uploads

Note: You can change the path file.upload-dir value. If you are a developer and want to implement an upload feature on your project, then please change the upload storage file location as per your requirement.

Step 3: Create a Pojo or Model Class and bind or assign the properties of application.properties file

Spring Boot provides us a very unique feature to automatically bind or assign the properties values to parameter present in a Pojo Class.

To achieve this we need to use the @ConfigurationProperties annotation in the Pojo Class as shown below.


package com.pixeltrice.uploaddownloadfileswithspringboot;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "file")
	public class FileStoragePojo {

		private String uploadDir;

	    public String getUploadDir() {
	        return uploadDir;
	    }

	    public void setUploadDir(String uploadDir) {
	        this.uploadDir = uploadDir;
	    }
	}

So in the above code prefix = “file” is used to find the properties which start with “file” which is present in the application.properties file and assigned that value to the uploadDir variable of FileStoragePojo. java class.

In our case, once you run the Spring Boot Application, uploadDir will be assigned with the value, C:/Users/1302143/uploads because in the application.properties file we have the only one property which starts with prefix “file” that is

file.upload-dir=C:/Users/1302143/uploads

Note: If you want to add more properties in the future which starts with “file” in the application.properties then you simply add the parameter inside the Pojo Class, so that it will automatically assign the value to that parameter.

Step 4: Enable the Configuration Properties in Main Class

In order to enable this property, we need to add @EnableConfigurationProperties annotation in the Main Class or the class which is tagged with @SpringBootApplication or you can add the @EnableConfigurationProperties to any other Configuration class.

package com.pixeltrice.uploaddownloadfileswithspringboot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;

@SpringBootApplication
@EnableConfigurationProperties({
    FileStoragePojo.class
})
public class UploadDownloadFilesWithSpringBootApplication {

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

}

Congratulation! You have completed all your configuration part, Now we can proceed further to write an actual line of code to upload and download the file.

Step 5: Create a Service class for Storing the file and retrieving it.

We will create a class named FileStorageService.java in order to store the file on our system and to retrieve it.

package com.pixeltrice.uploaddownloadfileswithspringboot;

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.nio.file.StandardCopyOption;

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;


@Service
public class FileStorageService {

    private final Path fileStorageLocation;
	
	
	
	@Autowired
    public FileStorageService(FileStoragePojo fileStoragePojo) {
        this.fileStorageLocation = Paths.get(fileStoragePojo.getUploadDir())
                .toAbsolutePath().normalize();

        try {
            Files.createDirectories(this.fileStorageLocation);
        } catch (Exception ex) {
            throw new FileStorageException("Unable to create the directory where the uploaded files will be stored.", ex);
        }
    }
	
	
	 public String storeFile(MultipartFile file) {
	        // Normalize file name
	        String fileName = StringUtils.cleanPath(file.getOriginalFilename());

	        try {
	            // Check if the file's name contains invalid characters
	            if(fileName.contains("..")) {
	                throw new FileStorageException("Sorry! Filename contains invalid path sequence " + fileName);
	            }

	            // Copy file to the target location (Replacing existing file with the same name)
	            Path targetLocation = this.fileStorageLocation.resolve(fileName);
	            Files.copy(file.getInputStream(), targetLocation, StandardCopyOption.REPLACE_EXISTING);

	            return fileName;
	        } catch (IOException ex) {
	            throw new FileStorageException("Could not store file " + fileName + ". Please try again!", ex);
	        }
	    }

	
	   public Resource loadFileAsResource(String fileName) {
	        try {
	            Path filePath = this.fileStorageLocation.resolve(fileName).normalize();
	            Resource resource = new UrlResource(filePath.toUri());
	            if(resource.exists()) {
	                return resource;
	            } else {
	                throw new MentionedFileNotFoundException("File not found " + fileName);
	            }
	        } catch (MalformedURLException ex) {
	            throw new MentionedFileNotFoundException("File not found " + fileName, ex);
	        }
	    }
	
	
}

Explanation of each line of code present in service class.

  1. Explanation of FileStorageService Constructor.
 public FileStorageService(FileStoragePojo fileStoragePojo) {
        this.fileStorageLocation = Paths.get(fileStoragePojo.getUploadDir())
                .toAbsolutePath().normalize();

        try {
            Files.createDirectories(this.fileStorageLocation);
        } catch (Exception ex) {
            throw new FileStorageException("Unable to create the directory where the uploaded files will be stored.", ex);
        }
    }
  • In the above line of code, we are defining a parameterized constructor, in which we are passing an object of FileStoragePojo which we already created in the above paragraph.
  • Assigning the path where the file will store after uploading.
this.fileStorageLocation = Paths.get(fileStoragePojo.getUploadDir())
                .toAbsolutePath().normalize();

  • Creating a folder or directory in that path where the uploaded files will save.
Files.createDirectories(this.fileStorageLocation);

2. Explanation of the Method named storeFile()

 public String storeFile(MultipartFile file) {
	        // Normalize file name
	        String fileName = StringUtils.cleanPath(file.getOriginalFilename());

	        try {
	            // Check if the file's name contains invalid characters
	            if(fileName.contains("..")) {
	                throw new FileStorageException("Sorry! Filename contains invalid path sequence " + fileName);
	            }

	            // Copy file to the target location (Replacing existing file with the same name)
	            Path targetLocation = this.fileStorageLocation.resolve(fileName);
	            Files.copy(file.getInputStream(), targetLocation, StandardCopyOption.REPLACE_EXISTING);

	            return fileName;
	        } catch (IOException ex) {
	            throw new FileStorageException("Could not store file " + fileName + ". Please try again!", ex);
	        }
	    }
  • In the first line of code, we are getting the actual name of the file from the path where it saved, For example, suppose we have uploaded a file with name springBoot.pdf in the following path C:/Users/1302143/uploads/springBoot.pdf.

Now above path, we are extracting the original name of the file that is springBoot.pdf with the use of the following line of code.

 String fileName = StringUtils.cleanPath(file.getOriginalFilename());

So the fileName = springBoot.pdf

Similarly, you can easily get an explanation for another line of the code, I have mentioned in the form of a comment.

Note: The whole purpose of storeFile() method is to return the original file name.

3. Explanation of Method loadFileAsResource(String fileName)

This method will return the stored or uploaded file in the form of Resource Object.

 public Resource loadFileAsResource(String fileName) {
	        try {
	            Path filePath = this.fileStorageLocation.resolve(fileName).normalize();
	            Resource resource = new UrlResource(filePath.toUri());
	            if(resource.exists()) {
	                return resource;
	            } else {
	                throw new MentionedFileNotFoundException("File not found " + fileName);
	            }
	        } catch (MalformedURLException ex) {
	            throw new MentionedFileNotFoundException("File not found " + fileName, ex);
	        }
	    }

Wonderful! I have you got some ideas about the code written in the Service class. If you noticed in the above code we are using some kind of Exception handling classes such as FileStorageException and MentionedFileNotFoundException.

Actually these exception is not a predefined class, we have to create it by ourselves.

Note: The code written inside the FileStorageService.java Service class we will use while creating an API in a further section.

Step 6: Creating a Exception Handling Classes

  1. FileStorageException.java
package com.pixeltrice.uploaddownloadfileswithspringboot;

	public class FileStorageException extends RuntimeException {
	    public FileStorageException(String message) {
	        super(message);
	    }

	    public FileStorageException(String message, Throwable cause) {
	        super(message, cause);
	    }
}

2. MentionedFileNotFoundException.java

package com.pixeltrice.uploaddownloadfileswithspringboot;

import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.http.HttpStatus;

@ResponseStatus(HttpStatus.NOT_FOUND)
	public class MentionedFileNotFoundException extends RuntimeException {
	    public MentionedFileNotFoundException(String message) {
	        super(message);
	    }

	    public MentionedFileNotFoundException(String message, Throwable cause) {
	        super(message, cause);
	    }
}

In the above code, I have used @ResponseStatus(HttpStatus.NOT_FOUND), which will return 404 status If the mentioned file has not found.

Step 7: Creating a Controller Class for API to uploading and downloading the files.

package com.pixeltrice.uploaddownloadfileswithspringboot;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

@RestController
public class FileStorageController {

	@Autowired
    private FileStorageService fileStorageService;
	
	 @PostMapping("/upload-single-file")
	    public UploadFileResponse uploadSingleFile(@RequestParam("file") MultipartFile file) {
	        String fileName = fileStorageService.storeFile(file);

	        String fileDownloadUri = ServletUriComponentsBuilder.fromCurrentContextPath()
	                .path("/download-file/")
	                .path(fileName)
	                .toUriString();

	        return new UploadFileResponse(fileName, fileDownloadUri,
	                file.getContentType(), file.getSize());
	    }
	 
	 @PostMapping("/upload-multiple-files")
	    public List<UploadFileResponse> uploadMultipleFiles(@RequestParam("files") MultipartFile[] files) {
	        return Arrays.asList(files)
	                .stream()
	                .map(file -> uploadSingleFile(file))
	                .collect(Collectors.toList());
	    }

	    @GetMapping("/download-file/{fileName:.+}")
	    public ResponseEntity<Resource> downloadFile(@PathVariable String fileName, HttpServletRequest request) {
	        // Load file as Resource
	        Resource resource = fileStorageService.loadFileAsResource(fileName);

	        // Try to determine file's content type
	        String contentType = null;
	        try {
	            contentType = request.getServletContext().getMimeType(resource.getFile().getAbsolutePath());
	        } catch (IOException ex) {
	            System.out.print("Could not determine file type.");
	        }

	        // Fallback to the default content type if type could not be determined
	        if(contentType == null) {
	            contentType = "application/octet-stream";
	        }

	        return ResponseEntity.ok()
	                .contentType(MediaType.parseMediaType(contentType))
	                .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + resource.getFilename() + "\"")
	                .body(resource);
	    }
}

Explanation of each api present inside the Controller Class.

  1. Upload File
 @PostMapping("/upload-single-file")
	    public UploadFileResponse uploadSingleFile(@RequestParam("file") MultipartFile file) {
	        String fileName = fileStorageService.storeFile(file);

	        String fileDownloadUri = ServletUriComponentsBuilder.fromCurrentContextPath()
	                .path("/download-file/")
	                .path(fileName)
	                .toUriString();

	        return new UploadFileResponse(fileName, fileDownloadUri,
	                file.getContentType(), file.getSize());
	    }

Above api is used to upload the single file and stored in the mentioned location on your system.

2. Upload Multiple Files

  @PostMapping("/upload-multiple-files")
	    public List<UploadFileResponse> uploadMultipleFiles(@RequestParam("files") MultipartFile[] files) {
	        return Arrays.asList(files)
	                .stream()
	                .map(file -> uploadSingleFile(file))
	                .collect(Collectors.toList());
	    }

With the use of above api we can upload a multiple files together.

3. Api to Download the file.

  @GetMapping("/download-file/{fileName:.+}")
	    public ResponseEntity<Resource> downloadFile(@PathVariable String fileName, HttpServletRequest request) {
	        // Load file as Resource
	        Resource resource = fileStorageService.loadFileAsResource(fileName);

	        // Try to determine file's content type
	        String contentType = null;
	        try {
	            contentType = request.getServletContext().getMimeType(resource.getFile().getAbsolutePath());
	        } catch (IOException ex) {
	            System.out.print("Could not determine file type.");
	        }

	        // Fallback to the default content type if type could not be determined
	        if(contentType == null) {
	            contentType = "application/octet-stream";
	        }

	        return ResponseEntity.ok()
	                .contentType(MediaType.parseMediaType(contentType))
	                .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + resource.getFilename() + "\"")
	                .body(resource);
	    }

If you noticed in the above code we are using one Object named UploadFileResponse as a return type. Basically this class we need to create and it is only responsible to properly return the response when that API gets trigger or called.

UploadFileResponse.java

package com.pixeltrice.uploaddownloadfileswithspringboot;

public class UploadFileResponse {

	    private String fileName;
	    private String fileDownloadUri;
	    private String fileType;
	    private long size;

	    public UploadFileResponse(String fileName, String fileDownloadUri, String fileType, long size) {
	        this.fileName = fileName;
	        this.fileDownloadUri = fileDownloadUri;
	        this.fileType = fileType;
	        this.size = size;
	    }

		public String getFileName() {
			return fileName;
		}

		public void setFileName(String fileName) {
			this.fileName = fileName;
		}

		public String getFileDownloadUri() {
			return fileDownloadUri;
		}

		public void setFileDownloadUri(String fileDownloadUri) {
			this.fileDownloadUri = fileDownloadUri;
		}

		public String getFileType() {
			return fileType;
		}

		public void setFileType(String fileType) {
			this.fileType = fileType;
		}

		public long getSize() {
			return size;
		}

		public void setSize(long size) {
			this.size = size;
		}
	    
	    
	    
}

Step 8: Run the Application and Test it from POSTMAN

  1. Upload a Single File

In this step, we call the API http://localhost:8080/upload-single-file from POSTMAN and pass the parameter in the form of Key-Value pair as shown in the figure. And once API gets called you will the response as shown below.

Upload and download the files using Spring Boot Application

Verify that file is uploaded on the path mentioned in the application.properties file C:/Users/1302143/uploads

Upload and download the files using Spring Boot Application

You can see in above figure that image has saved in the mentioned path.

2. Upload Multiple File

Call the API http://localhost:8080/upload-multiple-files and upload more than one file, it can be anything images, pdf or any other format.

Upload and download the files using Spring Boot Application

Here I have uploaded three files of type .txt, pdf, png, let us verify it stored on the mentioned path C:/Users/1302143/uploads.

Upload and download the files using Spring Boot Application

Great! You can see in the above figure, all three files have been saved in the mentioned location.

3. Download File

Go to POSTMAN and enter the following API http://localhost:8080/download-file/Core_java.pdf Make sure that fileName must be correct.

And instead of Click on Send button, first choose to Send and Download, as you can see in the below figure. Your file will be downloaded.

Upload and download the files using Spring Boot Application

Alright! Finally, we have built the RestFul API for downloading and uploading the multiple files.

Note: If you want the code for the front end, please Download the Source code from the below link.

You just need to add three files for the front end that is main.css, index.html, and main.js in following path src\main\resources\static under eclipse workspace.

Download Source Code.

Final Structure of Folder in Eclipse.

Upload and download the files using Spring Boot Application

Summary

Thank You so much for reading this article. In this article, we learned how to implement the feature to upload and download the file using spring boot. If you have any queries or doubts, please feel free to ask in the comment box.

You can also check out my article.

Leave a Reply

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