Handling file uploads may seem trivial — until your application needs to manage large files, high concurrency, or cloud storage. A naive implementation can lead to memory issues, slow performance, and unreliable delivery.

This guide shows how to build a scalable file upload system with Spring Boot, focusing on multipart handling, streaming support, security, and integration with cloud services like Amazon S3.


Basic Multipart Upload with Spring Boot

Spring Boot provides out-of-the-box support for file uploads via MultipartFile.

@PostMapping("/upload")
public ResponseEntity<String> handleFileUpload(@RequestParam("file") MultipartFile file) {
String filename = file.getOriginalFilename();
Path destination = Paths.get("uploads").resolve(filename);
try {
Files.copy(file.getInputStream(), destination);
return ResponseEntity.ok("Uploaded successfully");
} catch (IOException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Upload failed");
}
}

Ensure multipart support is enabled (usually by default):

spring:
servlet:
multipart:
enabled: true
max-file-size: 100MB
max-request-size: 100MB

Streaming Large File Uploads

To handle large files efficiently, avoid loading the entire file into memory. Instead, stream data directly to disk or cloud storage.

@PostMapping("/upload-stream")
public ResponseEntity<String> uploadStream(HttpServletRequest request) {
try (InputStream inputStream = request.getInputStream()) {
Path output = Paths.get("uploads/largefile.dat");
Files.copy(inputStream, output, StandardCopyOption.REPLACE_EXISTING);
return ResponseEntity.ok("Uploaded via streaming");
} catch (IOException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Streaming upload failed");
}
}

This is crucial when files exceed hundreds of MB or when scaling horizontally.


Asynchronous Processing with Queues

For better throughput, decouple file upload and processing using asynchronous queues (e.g., RabbitMQ, Kafka).

  1. Accept the file upload
  2. Store it temporarily (local or cloud)
  3. Push a message to the queue with file metadata
  4. Background worker processes the file (thumbnail, virus scan, etc.)

This improves responsiveness and fault-tolerance.


Cloud Storage Integration (AWS S3)

Offload file storage to Amazon S3 for better scalability and durability.

Maven dependency:

<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
</dependency>

Upload to S3:

S3Client s3 = S3Client.create();

PutObjectRequest request = PutObjectRequest.builder()
.bucket("my-bucket")
.key("uploads/" + file.getOriginalFilename())
.build();

s3.putObject(request, RequestBody.fromInputStream(file.getInputStream(), file.getSize()));

This keeps your web server stateless and shifts storage concerns to the cloud.


File Upload Security Best Practices

  • Limit file types via MIME type and extension validation
  • Set reasonable file size limits
  • Use virus scanning tools (e.g., ClamAV)
  • Prevent path traversal by sanitizing filenames
  • Store files with randomized filenames to prevent conflicts

Example filename sanitation:

String cleanName = StringUtils.cleanPath(file.getOriginalFilename());
String safeName = UUID.randomUUID() + "-" + cleanName;

Tracking Upload Progress

Implement progress tracking with a frontend that polls upload status or uses WebSockets.

Or use libraries like Resumable.js or Tus.io for resumable uploads.

On the backend, store temporary progress metadata in:

  • In-memory cache (e.g., caffeine)
  • Redis
  • Database row per upload

Horizontal Scaling Considerations

When scaling across nodes:

  • Store uploads in shared storage (cloud buckets, network-attached)
  • Use stateless services for upload endpoints
  • Add a load balancer for even distribution
  • Store metadata in a database, not in memory

For large-scale uploads, use presigned URLs to upload directly to cloud storage:

PresignedPutObjectRequest presigned = s3Utilities.presignPutObject(r -> r.signatureDuration(Duration.ofMinutes(10))
.putObjectRequest(PutObjectRequest.builder()
.bucket("my-bucket").key("upload-key").build()));

Client uploads directly to the cloud — your server just issues the signed URL.


Logging and Monitoring

Log key events such as:

  • Upload started/completed
  • File size and name
  • Upload duration
  • Storage success/failure

Send metrics to Prometheus/Grafana or use AWS CloudWatch to visualize traffic and file sizes over time.

Use Micrometer for metrics:

Timer uploadTimer = meterRegistry.timer("file.upload.duration");
uploadTimer.record(() -> performUpload());

Conclusion

Building a scalable file upload system with Spring Boot requires more than a MultipartFile handler. By supporting streaming uploads, integrating with cloud storage, and designing with asynchronous processing and security in mind, you can handle large volumes of uploads efficiently and reliably.

Whether you’re building a media platform or a document management system, these patterns will help you scale safely and confidently.