package com.example.demo.controller;
import com.example.demo.dto.FileMetadata;
import com.example.demo.service.FileStorageService;
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 jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.List;
@RestController
@RequestMapping("/api/files")
public class FileUploadController {
private final FileStorageService fileStorageService;
public FileUploadController(FileStorageService fileStorageService) {
this.fileStorageService = fileStorageService;
}
@PostMapping("/upload")
public ResponseEntity<FileMetadata> uploadFile(
@RequestParam("file") MultipartFile file
) {
FileMetadata metadata = fileStorageService.storeFile(file);
return ResponseEntity.ok(metadata);
}
@PostMapping("/upload-multiple")
public ResponseEntity<List<FileMetadata>> uploadMultipleFiles(
@RequestParam("files") List<MultipartFile> files
) {
List<FileMetadata> metadataList = files.stream()
.map(fileStorageService::storeFile)
.toList();
return ResponseEntity.ok(metadataList);
}
@GetMapping("/download/{fileName:.+}")
public ResponseEntity<Resource> downloadFile(
@PathVariable String fileName,
HttpServletRequest request
) throws IOException {
Resource resource = fileStorageService.loadFileAsResource(fileName);
String contentType = request.getServletContext()
.getMimeType(resource.getFile().getAbsolutePath());
if (contentType == null) {
contentType = "application/octet-stream";
}
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(contentType))
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + resource.getFilename() + "\"")
.body(resource);
}
@DeleteMapping("/{fileName:.+}")
public ResponseEntity<Void> deleteFile(@PathVariable String fileName) {
fileStorageService.deleteFile(fileName);
return ResponseEntity.noContent().build();
}
}
package com.example.demo.service;
import com.example.demo.dto.FileMetadata;
import com.example.demo.exception.FileStorageException;
import org.springframework.beans.factory.annotation.Value;
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.Paths;
import java.nio.file.StandardCopyOption;
import java.util.UUID;
@Service
public class FileStorageService {
private final Path fileStorageLocation;
public FileStorageService(@Value("${file.upload-dir:uploads}") String uploadDir) {
this.fileStorageLocation = Paths.get(uploadDir).toAbsolutePath().normalize();
try {
Files.createDirectories(this.fileStorageLocation);
} catch (Exception ex) {
throw new FileStorageException("Could not create upload directory", ex);
}
}
public FileMetadata storeFile(MultipartFile file) {
String originalFileName = StringUtils.cleanPath(file.getOriginalFilename());
// Validate file
if (originalFileName.contains("..")) {
throw new FileStorageException("Invalid file path: " + originalFileName);
}
if (file.isEmpty()) {
throw new FileStorageException("Cannot store empty file");
}
// Generate unique filename
String extension = originalFileName.substring(originalFileName.lastIndexOf("."));
String storedFileName = UUID.randomUUID().toString() + extension;
try {
Path targetLocation = this.fileStorageLocation.resolve(storedFileName);
Files.copy(file.getInputStream(), targetLocation,
StandardCopyOption.REPLACE_EXISTING);
FileMetadata metadata = new FileMetadata();
metadata.setFileName(storedFileName);
metadata.setOriginalFileName(originalFileName);
metadata.setContentType(file.getContentType());
metadata.setSize(file.getSize());
return metadata;
} catch (IOException ex) {
throw new FileStorageException("Failed to store file " + originalFileName, 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 FileStorageException("File not found: " + fileName);
}
} catch (MalformedURLException ex) {
throw new FileStorageException("File not found: " + fileName, ex);
}
}
public void deleteFile(String fileName) {
try {
Path filePath = this.fileStorageLocation.resolve(fileName).normalize();
Files.deleteIfExists(filePath);
} catch (IOException ex) {
throw new FileStorageException("Failed to delete file: " + fileName, ex);
}
}
}
Spring Boot handles multipart file uploads efficiently. MultipartFile represents uploaded files. I validate file types, sizes, and content. Files are stored locally, in cloud storage (S3, Azure Blob), or databases. Streaming large files prevents memory issues. Content-Disposition headers enable downloads with custom filenames. MIME types ensure proper browser handling. Virus scanning protects against malware. Asynchronous processing handles large uploads. Temporary file cleanup prevents disk space issues. Configuration limits max file size and request size. File metadata—original name, content type, size—is persisted. Proper error handling covers upload failures, storage issues, and validation errors. Security considerations include path traversal prevention and access control.