Spring Boot: File Upload with MultipartFile
Spring Boot handles multipart file uploads through the MultipartFile interface. The embedded Tomcat servlet handles the multipart parsing; your controller receives ready-to-use file objects with metadata (original name, size, content type) and the raw byte content.
Contents
Multipart Config
Add the following entries to your application.properties
# application.properties
spring.servlet.multipart.enabled=true
spring.servlet.multipart.max-file-size=10MB # max size per file
spring.servlet.multipart.max-request-size=50MB # max total request size
spring.servlet.multipart.location=/tmp # temp dir for parts
max-file-size=1MBmax-request-size=10MBMaxUploadSizeExceededException@ControllerAdvice@PostMapping with MultipartFile
Accept an upload with a MultipartFile@PostMapping
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
@RestController
@RequestMapping("/files")
public class FileController {
private final FileStorageService storageService;
public FileController(FileStorageService storageService) {
this.storageService = storageService;
}
@PostMapping("/upload")
public ResponseEntity<Map<String, String>> uploadFile(
@RequestParam("file") MultipartFile file) {
if (file.isEmpty()) {
return ResponseEntity.badRequest()
.body(Map.of("error", "File must not be empty"));
}
String savedName = storageService.store(file);
return ResponseEntity.ok(Map.of(
"filename", savedName,
"originalName", file.getOriginalFilename(),
"size", String.valueOf(file.getSize()),
"contentType", file.getContentType()
));
}
}
Saving to Disk
This service writes the uploaded file to a directory on disk.
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.nio.file.*;
import java.util.UUID;
@Service
public class FileStorageService {
private final Path uploadDir = Paths.get("uploads");
public FileStorageService() throws IOException {
Files.createDirectories(uploadDir);
}
public String store(MultipartFile file) {
try {
// Generate a unique name to avoid collisions
String extension = getExtension(file.getOriginalFilename());
String uniqueName = UUID.randomUUID() + "." + extension;
Path destination = uploadDir.resolve(uniqueName).normalize().toAbsolutePath();
// Security check — prevent path traversal
if (!destination.startsWith(uploadDir.toAbsolutePath())) {
throw new StorageException("Cannot store file outside upload directory");
}
file.transferTo(destination);
return uniqueName;
} catch (IOException e) {
throw new StorageException("Failed to store file", e);
}
}
private String getExtension(String filename) {
if (filename == null || !filename.contains(".")) return "bin";
return filename.substring(filename.lastIndexOf('.') + 1);
}
}
Validating File Type and Size
Check the size and content type yourself before saving; the multipart size limits do not check the type.
public void validate(MultipartFile file) {
// Size check (in addition to multipart limits)
long maxSizeBytes = 5 * 1024 * 1024; // 5 MB
if (file.getSize() > maxSizeBytes) {
throw new InvalidFileException("File size exceeds 5 MB limit");
}
// Content type whitelist
List<String> allowedTypes = List.of("image/jpeg", "image/png", "application/pdf");
String contentType = file.getContentType();
if (contentType == null || !allowedTypes.contains(contentType)) {
throw new InvalidFileException("File type not allowed: " + contentType);
}
// Extension check (defence in depth — content type can be spoofed)
String name = file.getOriginalFilename();
if (name == null) throw new InvalidFileException("Filename is missing");
String ext = name.toLowerCase();
if (!ext.endsWith(".jpg") && !ext.endsWith(".jpeg")
&& !ext.endsWith(".png") && !ext.endsWith(".pdf")) {
throw new InvalidFileException("File extension not allowed");
}
}
Multiple File Upload
To accept several files, bind the request part to a list of MultipartFile
@PostMapping("/upload-multiple")
public ResponseEntity<List<String>> uploadMultiple(
@RequestParam("files") List<MultipartFile> files) {
List<String> savedNames = files.stream()
.filter(f -> !f.isEmpty())
.map(storageService::store)
.toList();
return ResponseEntity.ok(savedNames);
}
HTML form for multiple file upload:
<form action="/files/upload-multiple" method="post" enctype="multipart/form-data">
<input type="file" name="files" multiple>
<button type="submit">Upload</button>
</form>
Downloading a File with ResponseEntity
Return the file as a ResourceContent-Disposition
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.http.*;
@GetMapping("/download/{filename}")
public ResponseEntity<Resource> downloadFile(@PathVariable String filename) {
try {
Path filePath = uploadDir.resolve(filename).normalize();
Resource resource = new UrlResource(filePath.toUri());
if (!resource.exists() || !resource.isReadable()) {
return ResponseEntity.notFound().build();
}
// Determine content type
String contentType = Files.probeContentType(filePath);
if (contentType == null) contentType = "application/octet-stream";
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(contentType))
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + resource.getFilename() + "\"")
.body(resource);
} catch (IOException e) {
return ResponseEntity.internalServerError().build();
}
}
// To display inline (e.g., images in browser) instead of downloading:
// .header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"" + name + "\"")