Works with any backend npm install Zero dependencies
← All Backend Guides

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>