Definition
- Default limits –
spring.servlet.multipart.max-file-sizeis ~1 MB per file;max-request-sizeis ~10 MB for the entire multipart request. - Exception – Exceeding either throws
MaxUploadSizeExceededExceptionwrapping the container'sSizeLimitExceededException. - Raise with properties – Set the values in
application.ymlor programmatically viaMultipartConfigFactory. - Wrong fix – Raising the limit on a controller that calls
getBytes()converts a clean 413 rejection into a catastrophic OutOfMemoryError. - Right fix – Stream with
getInputStream()for medium files; use presigned URLs for large files.
National Bank of India's online form has a small letterbox slot big enough for a one-page form (1 MB). When Rajesh tries to post a thick 50 MB loan file, it gets rejected. You can widen the slot (raise limits), but if you make it huge and a thousand customers shove thick files through at once, the mailroom floor (heap) is buried and the whole branch collapses. The real fix is a conveyor belt that carries pages straight to the warehouse as they arrive (streaming), or a gate pass for direct warehouse delivery (presigned URL).
Understanding the Concepts
Why does a Spring Boot MultipartFile upload fail past a few MB?
Spring Boot's autoconfiguration sets guardrails so a huge or malicious upload cannot exhaust server resources. Exceeding either limit throws MaxUploadSizeExceededException.
package com.bank.upload.controller;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
public class UploadController {
// When Rajesh uploads a 50 MB file with default limits:
// Tomcat rejects it before the controller even sees it.
// Exception: MaxUploadSizeExceededException
// caused by: SizeLimitExceededException
// "the request was rejected because its size (50000000)
// exceeds the configured maximum (1048576)"
public String handleUpload(MultipartFile file) {
// Never reached for files > 1 MB with defaults
return "Uploaded: " + file.getOriginalFilename();
}
}
What are the default limits?
| Property | Default | Meaning |
|---|---|---|
| spring.servlet.multipart.max-file-size | ~1 MB | Maximum size of any single uploaded file |
| spring.servlet.multipart.max-request-size | ~10 MB | Maximum size of the entire multipart request |
| spring.servlet.multipart.file-size-threshold | 0 | Size above which parts spool to disk instead of heap |
How do you raise them with properties?
In application.yml, set the values to allow larger files.
# application.yml
spring:
servlet:
multipart:
max-file-size: 50MB
max-request-size: 60MB
file-size-threshold: 256KB # spill to disk above this
How do you raise them programmatically?
Define a MultipartConfigElement bean from MultipartConfigFactory.
package com.bank.upload.config;
import jakarta.servlet.MultipartConfigElement;
import org.springframework.boot.web.servlet.MultipartConfigFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.unit.DataSize;
@Configuration
public class MultipartConfig {
@Bean
public MultipartConfigElement multipartConfigElement() {
MultipartConfigFactory factory = new MultipartConfigFactory();
factory.setMaxFileSize(DataSize.ofMegabytes(50));
factory.setMaxRequestSize(DataSize.ofMegabytes(60));
factory.setFileSizeThreshold(DataSize.ofKilobytes(256));
return factory.createMultipartConfig();
}
}
Why is raising the limit the wrong fix for truly large files?
The real danger is how the file is consumed. If your controller calls getBytes(), it loads the entire file into the heap as a byte array. Raising the limit just converts a clean 413 rejection into a catastrophic OOM crash.
package com.bank.upload.antipattern;
import org.springframework.web.multipart.MultipartFile;
public class RaiseLimitWrong {
// WRONG: raised limit to 5 GB, but still calls getBytes()
public void handleUpload(MultipartFile file) throws Exception {
byte[] data = file.getBytes(); // 5 GB in heap for ONE request
// A handful of concurrent uploads = instant OutOfMemoryError
// The JVM crashes, taking down ALL other users' requests.
}
}
How do you stream the InputStream correctly?
Instead of getBytes(), call getInputStream() and copy in fixed-size chunks. Only one chunk is in memory at a time.
package com.bank.upload.service;
import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
public class StreamingUploadService {
// CORRECT: stream in chunks, memory stays bounded
public void handleUpload(MultipartFile file, Path destination) throws Exception {
try (InputStream in = file.getInputStream();
OutputStream out = Files.newOutputStream(destination)) {
byte[] buffer = new byte[8192]; // 8 KB at a time
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
}
// Only 8 KB in memory at any time, regardless of file size
}
// Or pipe directly to S3:
// s3Client.putObject(putRequest,
// RequestBody.fromInputStream(file.getInputStream(), file.getSize()));
}
When should you use which mechanism?
Match the mechanism to the file size.
| File Size | Mechanism | Why |
|---|---|---|
| Small (< 10 MB) | Default MultipartFile + raised limits | Simple, getBytes() is safe |
| Medium (10-500 MB) | MultipartFile + stream InputStream | Bounded memory |
| Large (500 MB+) | Presigned URL (direct-to-S3) | Server never touches bytes |
| Very Large (multi-GB) | S3 Multipart Upload | Resumability, parallelism |
package com.bank.upload.service;
public class UploadStrategySelector {
public String selectStrategy(long sizeBytes) {
if (sizeBytes < 10_000_000) {
return "MultipartFile with getBytes() (simple, safe for small files)";
} else if (sizeBytes < 500_000_000) {
return "MultipartFile with getInputStream() (stream, bounded memory)";
} else {
return "Presigned URL direct-to-S3 (server never touches bytes)";
}
}
}
How do you handle the exception gracefully?
Catch MaxUploadSizeExceededException in a @ControllerAdvice and return a clean 413.
package com.bank.upload.exception;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
@ControllerAdvice
public class UploadExceptionHandler {
@ExceptionHandler(MaxUploadSizeExceededException.class)
public ResponseEntity<String> handleMaxUploadSize(
MaxUploadSizeExceededException ex) {
return ResponseEntity
.status(HttpStatus.PAYLOAD_TOO_LARGE) // 413
.body("File too large. Maximum allowed size is 50 MB.");
}
}
What are common interview follow-up questions?
What is the difference between file-size-threshold and max-file-size? How does Tomcat handle multipart parsing internally? What happens if the temp directory fills up during spooling? How would you implement progress tracking for a large upload? What is the difference between StandardServletMultipartResolver and CommonsMultipartResolver?
Visual Overview
┌──────────────────────────────────────────────────────────┐ │ SPRING BOOT MULTIPART UPLOAD DECISION TREE │ ├──────────────────────────────────────────────────────────┤ │ │ │ File size? │ │ │ │ │ ├── < 1 MB ──▶ Default limits OK, getBytes() safe │ │ │ │ │ ├── 1-50 MB ──▶ Raise limits in application.yml │ │ │ stream getInputStream() to dest │ │ │ │ │ ├── 50-500 MB ──▶ Raise limits + stream │ │ │ consider WebFlux for concurrency │ │ │ │ │ └── 500 MB+ ──▶ Presigned URL (direct to S3) │ │ S3 multipart for multi-GB │ │ Server never touches bytes │ │ │ │ WRONG: raise to 5 GB + getBytes() │ │ → 5 GB in heap per request → OutOfMemoryError │ │ │ │ CORRECT: match mechanism to size │ │ → small: getBytes() │ medium: stream │ large: presign │ └──────────────────────────────────────────────────────────┘
Key Takeaway
Spring Boot's default multipart limits exist to protect the server: max-file-size is about 1 MB per file and max-request-size is about 10 MB for the whole request. When Rajesh tried uploading a 50 MB loan document at National Bank of India, it was rejected with MaxUploadSizeExceededException. The first fix is to catch that exception in a ControllerAdvice and return a clean 413 so the client understands.
To allow larger files, Pankaj raised the limits in application.yml and also set a file-size-threshold so parts above that size spill to disk instead of sitting in the heap. But raising the limit is the wrong answer for truly large files. The real constraint is memory, not the configured number. If the controller calls getBytes(), it loads the entire file as a byte array. After the team bumped the limit to 5 GB, a few concurrent 40 MB uploads buried the heap because the code still called getBytes(), converting a clean rejection into a catastrophic OOM crash.
The correct approach is to stream: call getInputStream() and copy in fixed-size chunks, or pipe directly to S3 with RequestBody.fromInputStream(), so only one chunk is in memory at a time. For truly large files (gigabytes), bypass the server entirely with presigned URLs or S3 multipart upload. The principle is to match the mechanism to the size: raise limits and getBytes() for small files, stream for medium, and presigned for large.
Remember one sentence: The default limits are ~1 MB per file and ~10 MB per request; raise them for modest files but stream the InputStream for large ones and go direct-to-S3 for truly large files, because raising limits on a getBytes() controller converts a clean 413 into an OutOfMemoryError.
---