In this article, we'll explore how to implement a secure file upload feature in a Spring Boot application that allows uploading files, validates them, saves metadata temporarily, and removes the metadata after processing to ensure security.
Steps Overview
- Upload the file.
- Validate file (size, type, etc.).
- Save metadata temporarily.
- Securely remove metadata after processing.
1. Project Setup
Dependencies (pom.xml)
Ensure you have the following dependencies in your Spring Boot project:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
</dependencies>
2. File Metadata Entity
import jakarta.persistence.*;
import java.time.LocalDateTime;
@Entity
public class FileMetadata {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String filename;
private String fileType;
private long fileSize;
private LocalDateTime uploadTime;
public FileMetadata() {}
public FileMetadata(String filename, String fileType, long fileSize, LocalDateTime uploadTime) {
this.filename = filename;
this.fileType = fileType;
this.fileSize = fileSize;
this.uploadTime = uploadTime;
}
}
3. Repository
import org.springframework.data.jpa.repository.JpaRepository;
public interface FileMetadataRepository extends JpaRepository<FileMetadata, Long> {
}
4. File Upload Service
import org.apache.commons.io.FilenameUtils;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.time.LocalDateTime;
@Service
public class FileUploadService {
private final FileMetadataRepository metadataRepository;
private final String UPLOAD_DIR = "uploads/";
public FileUploadService(FileMetadataRepository metadataRepository) {
this.metadataRepository = metadataRepository;
}
public String uploadFile(MultipartFile file) throws IOException {
validateFile(file);
String filename = System.currentTimeMillis() + "_" + file.getOriginalFilename();
Path uploadPath = Path.of(UPLOAD_DIR, filename);
Files.copy(file.getInputStream(), uploadPath, StandardCopyOption.REPLACE_EXISTING);
FileMetadata metadata = new FileMetadata(filename, file.getContentType(), file.getSize(), LocalDateTime.now());
metadataRepository.save(metadata);
removeMetadata(metadata.getId());
return "File uploaded successfully: " + filename;
}
private void validateFile(MultipartFile file) {
String extension = FilenameUtils.getExtension(file.getOriginalFilename());
long maxFileSize = 5 * 1024 * 1024;
if (file.isEmpty()) {
throw new RuntimeException("File is empty.");
}
if (file.getSize() > maxFileSize) {
throw new RuntimeException("File size exceeds limit.");
}
if (!extension.matches("jpg|png|pdf|txt")) {
throw new RuntimeException("Invalid file type.");
}
}
private void removeMetadata(Long id) {
new Thread(() -> {
try {
Thread.sleep(5000);
metadataRepository.deleteById(id);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();
}
}
5. File Upload Controller
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
@RestController
@RequestMapping("/api/files")
public class FileUploadController {
private final FileUploadService fileUploadService;
public FileUploadController(FileUploadService fileUploadService) {
this.fileUploadService = fileUploadService;
}
@PostMapping("/upload")
public ResponseEntity<String> uploadFile(@RequestParam("file") MultipartFile file) {
try {
String response = fileUploadService.uploadFile(file);
return ResponseEntity.ok(response);
} catch (IOException e) {
return ResponseEntity.badRequest().body("File upload failed: " + e.getMessage());
}
}
}
6. Application Configuration
application.properties:
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.servlet.multipart.max-file-size=5MB
spring.servlet.multipart.max-request-size=5MB