Java Spring Boot Rest API to Upload/Download File on Server
This article demonstrates how to use a Spring Boot application and MySQL to maintain a database of uploaded files and their metadata.
Join the DZone community and get the full member experience.
Join For FreeI will be using Spring Initialzr to generate my Spring Boot application, include dependencies while creating a project. Also, I will be using the MySQL database to keep track of files uploaded and their metadata.
This API will be helpful for those who are registering new users to their business/application and want users to upload documents for verification.
Set up application.properties file with the database, upload the directory, and other details:
spring.datasource.url = jdbc:mysql://127.0.0.1:8889/merchant
spring.datasource.username = root
spring.datasource.password = root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.hibernate.ddl-auto=update
spring.jpa.database-platform=org.hibernate.dialect.MySQL57Dialect
spring.jpa.generate-ddl=true
spring.jpa.show-sql=true
spring.application.name=media-service
server.port=8083
## MULTIPART (MultipartProperties)
# 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=200MB
# Max Request Size
spring.servlet.multipart.max-request-size=215MB
# All files uploaded through the REST API will be stored in this directory
file.upload-dir=/home/yogesh/media/upload
Create an entity class, DocumnentStorageProperties.java, to save information about a file uploaded and to avoid the duplication of files (you can skip this if you don't want to keep metadata).
Here I am keeping my database name as merchant and table name as merchant_documents. There's no need to create the table manually as I have set up spring.jpa.generate-ddl=true
in properties. It will fire the DDL command when the application starts to create a table according to the Entity class defined in Java.
x
prefix = "file") (
name = "merchant_documents") (
public class DocumnentStorageProperties {
(strategy = GenerationType.AUTO)
(name = "document_id")
private Integer documentId;
(name = "user_id")
private Integer UserId;
(name = "file_name")
private String fileName;
(name = "document_type")
private String documentType;
(name = "document_format")
private String documentFormat;
(name = "upload_dir")
private String uploadDir;
// Getter and Setter
In the above entity class, the upload_dir
variable will get initialized by the value which we have set in application.properties(file.upload-dir).
Create a repository interface for the above entity to get built-in CRUD operations method support. Add our own new method to check whether a particular file is present for that user or not.
xxxxxxxxxx
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import com.pocketutility.mediaservice.file.DocumnentStorageProperties;
public interface DocumentStoragePropertiesRepo extends JpaRepository<DocumnentStorageProperties, Integer> {
("Select a from DocumnentStorageProperties a where user_id = ?1 and document_type = ?2")
DocumnentStorageProperties checkDocumentByUserId(Integer userId, String docType);
("Select fileName from DocumnentStorageProperties a where user_id = ?1 and document_type = ?2")
String getUploadDocumnetPath(Integer userId, String docType);
}
Create a Service class to store and download files on the server, and to store information in the database. Here I have used a few more new classes which I will be explaining in a later section of this article, so if you get any compilation issues, ignore them for the moment.
xxxxxxxxxx
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.FileNotFoundException;
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;
public class DocumentStorageService {
private final Path fileStorageLocation;
DocumentStoragePropertiesRepo docStorageRepo;
public DocumentStorageService(DocumnentStorageProperties fileStorageProperties) {
this.fileStorageLocation = Paths.get(fileStorageProperties.getUploadDir())
.toAbsolutePath().normalize();
try {
Files.createDirectories(this.fileStorageLocation);
} catch (Exception ex) {
throw new DocumentStorageException("Could not create the directory where the uploaded files will be stored.", ex);
}
}
public String storeFile(MultipartFile file, Integer userId, String docType) {
// Normalize file name
String originalFileName = StringUtils.cleanPath(file.getOriginalFilename());
String fileName = "";
try {
// Check if the file's name contains invalid characters
if(originalFileName.contains("..")) {
throw new DocumentStorageException("Sorry! Filename contains invalid path sequence " + originalFileName);
}
String fileExtension = "";
try {
fileExtension = originalFileName.substring(originalFileName.lastIndexOf("."));
} catch(Exception e) {
fileExtension = "";
}
fileName = userId + "_" + docType + fileExtension;
// 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);
DocumnentStorageProperties doc = docStorageRepo.checkDocumentByUserId(userId, docType);
if(doc != null) {
doc.setDocumentFormat(file.getContentType());
doc.setFileName(fileName);
docStorageRepo.save(doc);
} else {
DocumnentStorageProperties newDoc = new DocumnentStorageProperties();
newDoc.setUserId(userId);
newDoc.setDocumentFormat(file.getContentType());
newDoc.setFileName(fileName);
newDoc.setDocumentType(docType);
docStorageRepo.save(newDoc);
}
return fileName;
} catch (IOException ex) {
throw new DocumentStorageException("Could not store file " + fileName + ". Please try again!", ex);
}
}
public Resource loadFileAsResource(String fileName) throws Exception {
try {
Path filePath = this.fileStorageLocation.resolve(fileName).normalize();
Resource resource = new UrlResource(filePath.toUri());
if(resource.exists()) {
return resource;
} else {
throw new FileNotFoundException("File not found " + fileName);
}
} catch (MalformedURLException ex) {
throw new FileNotFoundException("File not found " + fileName);
}
}
public String getDocumentName(Integer userId, String docType) {
return docStorageRepo.getUploadDocumnetPath(userId, docType);
}
}
There are two important methods in the Service class; one is storeFile
, which will store the file at the required location and save the file details in the database. Before storing the file, we will check whether a user has already given the type of file available, and if it is available, then replace the latest file on a server and update the database with the latest information. Otherwise, it will create a new entry in the database and save the file at the required location. We will be keeping the file name as <userId>_<docType>.<extension>
just to avoid ambiguity in the file name.
The second method is loadFileAsResource
which returns the file as a Resource on the basis of file name given.
Also, we have an autowired repository object to this service which we have created in thr previous step and autowired fileStorageLocation
on basis of upload-dir properties.
Now let's create a controller that will handle the HTTP request and send the proper response back.
xxxxxxxxxx
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.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import com.pocketutility.mediaservice.file.UploadFileResponse;
import com.pocketutility.mediaservice.service.DocumentStorageService;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class DocumentController {
private DocumentStorageService documneStorageService;
("/uploadFile")
public UploadFileResponse uploadFile( ("file") MultipartFile file,
("userId") Integer UserId,
("docType") String docType) {
String fileName = documneStorageService.storeFile(file, UserId, docType);
String fileDownloadUri = ServletUriComponentsBuilder.fromCurrentContextPath()
.path("/downloadFile/")
.path(fileName)
.toUriString();
return new UploadFileResponse(fileName, fileDownloadUri,
file.getContentType(), file.getSize());
}
("/downloadFile")
public ResponseEntity<Resource> downloadFile( ("userId") Integer userId,
("docType") String docType,
HttpServletRequest request) {
String fileName = documneStorageService.getDocumentName(userId, docType);
Resource resource = null;
if(fileName !=null && !fileName.isEmpty()) {
try {
resource = documneStorageService.loadFileAsResource(fileName);
} catch (Exception e) {
e.printStackTrace();
}
// Try to determine file's content type
String contentType = null;
try {
contentType = request.getServletContext().getMimeType(resource.getFile().getAbsolutePath());
} catch (IOException ex) {
//logger.info("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);
} else {
return ResponseEntity.notFound().build();
}
}
}
So the above controller has two mappings:
- For uploading file
- Request URL: /uploadFile
- Request Parameters: Actual file, userId, docType
- Response: Will return JSON having file information(Shown in last part of this article)
- For downloading file
- Request URL: /downloadFile
- Request Parameters: userId and docType
- Response: Will return the file in attachment along with content-type and other details. (If file not found for that user it will return 404 Not found code)
Other Classes which we have used are:
UploadFileResponse
: To send the response back when the upload is successful.
xxxxxxxxxx
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;
}
// Getter and Setter
DocumentStorageException
: Our own Custom exception type
xxxxxxxxxx
public class DocumentStorageException extends RuntimeException {
public DocumentStorageException(String message) {
super(message);
}
public DocumentStorageException(String message, Throwable cause) {
super(message, cause);
}
}
In order to secure your API with Spring Basic Auth add below the class. Update the required user name and password (use online Bcrypt encoder to encode your password).
x
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.JdbcUserDetailsManager;
public class ApiSecurity extends WebSecurityConfigurerAdapter {
public BCryptPasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests().anyRequest().fullyAuthenticated().and()
.httpBasic();
}
public void configureGlobal(AuthenticationManagerBuilder authenticationMgr)
throws Exception {
authenticationMgr.inMemoryAuthentication().withUser("username")
.password("$6y$13$9OzrtK7X4mad/te8m7uhysjfaihjsdfZdJ/fDZFaOJjrfFbYzYtzky").roles("ADMIN");
}
}
Testing using Postman:
Post request to upload a file:
GET request to download the file:
Database screenshot:
Verify that file is visible at the required location.
Opinions expressed by DZone contributors are their own.
Comments