Handle MultipleUpload file uploads with Spring Boot using
MultipartFile and a REST controller.
This guide covers both standard multipart uploads and chunked uploads.
Configure file upload limits in application.properties.
# application.properties spring.servlet.multipart.max-file-size=200MB spring.servlet.multipart.max-request-size=200MB upload.directory=./uploads
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("*")
.allowedMethods("POST", "OPTIONS")
.allowedHeaders(
"Content-Type", "X-Upload-Id", "X-Chunk-Index",
"X-Chunk-Count", "X-File-Name", "X-File-Size"
);
}
}
The controller receives the file through Spring's MultipartFile interface and saves it with a unique GUID.
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.nio.file.*;
import java.util.*;
@RestController
@RequestMapping("/api")
public class UploadController {
@Value("${upload.directory:./uploads}")
private String uploadDir;
@PostMapping("/upload")
public ResponseEntity<Map<String, Object>> upload(
@RequestParam("file") MultipartFile file) throws IOException {
Path uploadPath = Paths.get(uploadDir);
Files.createDirectories(uploadPath);
String fileGuid = UUID.randomUUID().toString();
String originalName = file.getOriginalFilename();
String ext = "";
if (originalName != null && originalName.contains(".")) {
ext = originalName.substring(originalName.lastIndexOf("."));
}
Path destPath = uploadPath.resolve(fileGuid + ext);
file.transferTo(destPath.toFile());
Map<String, Object> response = new LinkedHashMap<>();
response.put("success", true);
response.put("fileGuid", fileGuid);
response.put("fileName", originalName);
response.put("fileSize", file.getSize());
return ResponseEntity.ok(response);
}
}
Each chunk arrives as a raw body with custom headers. Store chunks in a temporary directory and assemble the final file once all chunks have arrived.
// Add to UploadController
@PostMapping("/upload-chunk")
public ResponseEntity<Map<String, Object>> uploadChunk(
@RequestHeader("X-Upload-Id") String uploadId,
@RequestHeader("X-Chunk-Index") int chunkIndex,
@RequestHeader("X-Chunk-Count") int chunkCount,
@RequestHeader("X-File-Name") String fileName,
@RequestHeader("X-File-Size") long fileSize,
@RequestBody byte[] chunkData) throws IOException {
Path chunkDir = Paths.get(uploadDir, "chunks", uploadId);
Files.createDirectories(chunkDir);
// Save this chunk
Path chunkPath = chunkDir.resolve("chunk_" + chunkIndex);
Files.write(chunkPath, chunkData);
// Count received chunks
long received;
try (var stream = Files.list(chunkDir)) {
received = stream.count();
}
if (received == chunkCount) {
// Assemble final file
String fileGuid = UUID.randomUUID().toString();
String ext = "";
if (fileName.contains(".")) {
ext = fileName.substring(fileName.lastIndexOf("."));
}
Path uploadPath = Paths.get(uploadDir);
Files.createDirectories(uploadPath);
Path finalPath = uploadPath.resolve(fileGuid + ext);
try (OutputStream output = Files.newOutputStream(finalPath)) {
for (int i = 0; i < chunkCount; i++) {
Path cp = chunkDir.resolve("chunk_" + i);
byte[] data = Files.readAllBytes(cp);
output.write(data);
}
}
// Clean up chunks
try (var stream = Files.list(chunkDir)) {
stream.forEach(p -> {
try { Files.delete(p); } catch (IOException ignored) {}
});
}
Files.delete(chunkDir);
Map<String, Object> response = new LinkedHashMap<>();
response.put("success", true);
response.put("fileGuid", fileGuid);
response.put("fileName", fileName);
response.put("fileSize", fileSize);
return ResponseEntity.ok(response);
}
Map<String, Object> response = new LinkedHashMap<>();
response.put("success", true);
response.put("chunksReceived", received);
return ResponseEntity.ok(response);
}
Both endpoints must return this JSON structure on successful upload completion.
{
"success": true,
"fileGuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"fileName": "photo.jpg",
"fileSize": 204800
}
Initialize MultipleUpload on the client, pointing to your Spring Boot API endpoints.
<link rel="stylesheet" href="multipleupload.css" />
<div id="uploader"></div>
<script src="multipleupload.js"></script>
<script>
var uploader = new MultipleUpload("#uploader", {
uploadUrl: "/api/upload",
chunkUploadUrl: "/api/upload-chunk",
chunkSize: 2 * 1024 * 1024, // 2 MB chunks
onFileUploaded: function (file, response) {
console.log("Uploaded:", response.fileName, response.fileGuid);
}
});
</script>