Works with any backend npm install Zero dependencies

Java Spring Boot

Handle MultipleUpload file uploads with Spring Boot using MultipartFile and a REST controller. This guide covers both standard multipart uploads and chunked uploads.

Application Properties

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

CORS Configuration

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"
 );
 }
}

Multipart Form Upload

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);
 }
}

Chunked Upload

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);
}

Expected JSON 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
}

Client-Side JavaScript

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>