Senior 9 min · May 23, 2026

File Upload in Spring Boot: MultipartFile, S3, Streaming & Validation

Master Spring Boot file upload with MultipartFile, S3 integration, streaming large files, size limits, and production-ready validation.

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer
  • Use @RequestParam MultipartFile file or @RequestBody with MultipartFile in a @RestController to accept uploads
  • Configure spring.servlet.multipart.max-file-size and spring.servlet.multipart.max-request-size in application.properties
  • Stream large files with InputStream to avoid heap exhaustion — never load the whole file into a byte array
  • Validate file type by checking magic bytes, not just the extension or Content-Type header
  • Use AWS SDK v2 S3AsyncClient with multipart upload for files over 100 MB in production
✦ Definition~90s read
What is File Upload in Spring Boot?

Spring Boot file upload is built on the Servlet 3.0 multipart API, abstracted behind Spring's MultipartFile interface. When a client sends a multipart/form-data POST, the servlet container (Tomcat by default) parses the stream into parts. Spring wraps each part in a StandardMultipartFile that gives you getInputStream(), getBytes(), getOriginalFilename(), getContentType(), and getSize().

Think of file upload like handing a package to a delivery service.

For files above the threshold set by spring.servlet.multipart.file-size-threshold (default 0), the container spills the data to a temporary disk file instead of keeping it in memory. This is controlled by spring.servlet.multipart.location. Understanding this spill behaviour is critical: if you run on a read-only filesystem (common in Kubernetes), you must set a writable temp path explicitly or uploads will fail silently.

Large file handling requires bypassing the standard multipart resolver entirely and reading the raw HttpServletRequest InputStream. This lets you pipe bytes directly to S3 or another sink without any intermediate buffer, keeping heap usage constant regardless of file size.

Plain-English First

Think of file upload like handing a package to a delivery service. Spring Boot is the counter — it needs to know the maximum package size it will accept, where to temporarily store it, and where to forward it. If the package is huge, you don't carry the whole thing at once; you pass it through a conveyor belt piece by piece (streaming), so you don't drop it.

At 2 AM your on-call phone rings. The service is throwing java.lang.OutOfMemoryError on every request. The root cause: a junior dev used file.getBytes() on a 500 MB video upload, loading the entire file into the JVM heap on every concurrent request. The pod crashed, and the autoscaler couldn't keep up. This is the most common file-upload mistake in Spring Boot, and it's entirely preventable.

File upload sounds simple — it's just bytes going from a browser to a server — but the production surface area is enormous. You need to think about request size limits at four layers: the client, the reverse proxy (Nginx/ALB), the Spring DispatchedServlet, and your application code. Missing any one layer causes mysterious 413 errors or silent truncation.

Multipart handling in Spring Boot changed significantly between Spring Boot 2 and 3. The underlying Tomcat, Jetty, or Undertow behaviour differs, and when you put a Kubernetes ingress in front, default body size limits of 1 MB will silently break uploads before your code even runs.

Then there's security. Accepting arbitrary files from the internet is an attack surface. Path traversal, polyglot files (a valid image that is also a ZIP bomb or a script), MIME type spoofing — each has a real CVE attached. Production file upload code must validate at the byte level, not at the filename level.

This guide covers everything from the basic MultipartFile handler through streaming with InputStream, virus scanning hooks, S3 multipart upload using AWS SDK v2, and the exact Actuator metrics you should alert on in production.

Basic MultipartFile Upload Endpoint

The simplest Spring Boot file upload endpoint uses @RequestParam MultipartFile file on a @PostMapping. This works for files that fit comfortably in memory — anything under ~50 MB on a well-tuned JVM. The critical first step is configuration: without explicit size limits, Spring Boot defaults to 1 MB for both max-file-size and max-request-size, which is useless for any real application.

Annotate your controller with @RestController and @RequestMapping. The MultipartFile parameter gives you access to getOriginalFilename(), getContentType(), getSize(), and getInputStream(). Never trust getOriginalFilename() or getContentType() — both come directly from the client and can be spoofed. A file named photo.jpg with Content-Type: image/jpeg can contain arbitrary bytes.

For small files, after validation, you can write to local disk with Files.copy(file.getInputStream(), destination, StandardCopyOption.REPLACE_EXISTING). For production systems, local disk is an antipattern — your pods are ephemeral. Write to S3, GCS, or Azure Blob Storage instead.

Always return a structured response — include the generated file ID (UUID), the storage path, the file size, and the detected MIME type. Never return the original filename in the response without sanitizing it, as it could contain path traversal sequences.

Never Trust Client-Supplied MIME Types
Always detect the actual file type using magic bytes (Apache Tika or similar). A client can set any Content-Type header. A PNG bomb or polyglot file can pass extension-based checks trivially.
Production Insight
Add file.getSize() logging at INFO level — unexpected size spikes in logs often precede OOM incidents by minutes.
Key Takeaway
Validate with magic bytes before processing; validate size before reading; return only server-generated IDs in responses.

Streaming Large Files to S3 with AWS SDK v2

For files over 50 MB, or when running at scale, streaming directly to S3 without intermediate buffering is the only production-viable approach. AWS SDK v2 introduced S3AsyncClient and AsyncRequestBody.fromInputStream(), which pipes the MultipartFile's InputStream directly to S3 over HTTP/2 without ever materializing the bytes on the JVM heap.

For files over 100 MB, use the S3TransferManager (built on top of S3AsyncClient) which automatically splits the upload into 8 MB chunks and uploads them in parallel via the S3 multipart upload API. This is dramatically faster on high-bandwidth connections and is resumable — if the connection drops mid-upload, only the failed parts need to be retried.

The tricky part is that MultipartFile.getInputStream() can only be read once. If you need to compute a checksum or validate the file type in a streaming fashion, you must compose the stream: wrap it in a DigestInputStream for MD5/SHA-256, then pipe it through your validator, then to S3, all in a single read pass. Apache Tika supports streaming detection with AutoDetectParser.

For very large files (video, datasets), consider presigned S3 URLs: the client uploads directly to S3, bypassing your server entirely. Your server generates the presigned URL, the client uploads, and S3 triggers an event notification to your backend via SQS. This eliminates your server from the upload path entirely, which is the correct architecture for files over 500 MB.

Always set a content MD5 or SHA-256 header on S3 puts. S3 verifies the checksum and rejects corrupted uploads. This catches network corruption that would otherwise silently store a truncated file.

Use Virtual Threads for S3 Streaming
Java 21 virtual threads are ideal for the blocking I/O in S3 streaming. Pass Executors.newVirtualThreadPerTaskExecutor() to AsyncRequestBody.fromInputStream() to avoid blocking platform threads.
Production Insight
S3 multipart upload leaves orphaned parts if the client disconnects mid-upload. Set an S3 lifecycle rule to abort incomplete multipart uploads after 1 day, or you'll accumulate invisible storage costs.
Key Takeaway
Stream via InputStream to S3 — never buffer to byte array. Use S3TransferManager for files over 100 MB and presigned URLs for files over 500 MB.

File Validation: Magic Bytes, Size Limits, and Virus Scanning

Production file upload validation has three layers: size limits (enforced at the framework level before your code runs), type validation (using magic bytes, not extensions), and content scanning (ClamAV or a commercial API for virus/malware detection).

Magic byte detection with Apache Tika is the standard approach. Tika reads the first few bytes of the stream and matches them against a database of known file type signatures. A JPEG always starts with FF D8 FF, a PNG with 89 50 4E 47, a PDF with 25 50 44 46. No amount of filename manipulation can fake these bytes.

For images, additionally validate that the file can actually be decoded as an image. Use ImageIO.read() wrapped in a try-catch — if it returns null or throws, the file is corrupt or a disguised binary. For a more thorough check, use the scrimage library which also detects image bombs (tiny files that expand to gigabytes when decoded).

Virus scanning in production typically uses ClamAV via its Unix socket. Run ClamAV as a sidecar in your Kubernetes pod and connect to its socket using the clamav4j library. Scan the input stream before storing it anywhere. For high-throughput systems, send files to a scanning queue asynchronously and move them from a quarantine bucket to the public bucket only after a clean scan result.

Path traversal prevention: never use file.getOriginalFilename() as a filesystem path. Always generate a UUID-based filename server-side. If you must preserve the original name for display, store it in a database field only — never use it for filesystem operations.

Image Bomb Protection Required
A 1 KB PNG can expand to 10 GB in memory when decoded. Always cap decoded image dimensions. The scrimage library's ImmutableImage.loader() has a built-in dimension limit option.
Production Insight
Log every rejected upload with the detected MIME type and the client IP — it's your earliest signal of an active attack or a misconfigured client.
Key Takeaway
Three-layer validation: framework size limits, Tika magic-byte type detection, and ClamAV virus scanning — in that order, failing fast at each layer.

Global Exception Handling for Upload Errors

Upload error handling is a user experience problem as much as a technical one. By default, Spring Boot returns a 500 with a stack trace when MaxUploadSizeExceededException is thrown. Users see a cryptic error; the frontend has no structured error to parse. A @ControllerAdvice exception handler converts these to proper HTTP responses with machine-readable error bodies.

The key exceptions to handle: MaxUploadSizeExceededException (size limit hit), MissingServletRequestPartException (no file in the request), HttpMediaTypeNotSupportedException (wrong Content-Type header), MultipartException (any parsing failure), and your own custom StorageException and ValidationException.

For the 413 case, there's a subtlety: if the size limit is hit at the Tomcat level, the exception is thrown before your controller runs, so your @ControllerAdvice catches it. But if the limit is hit at the Nginx level, Nginx never forwards the request — your advice is never called. Document this distinction in your runbook.

Always include a trace-id (from MDC or Sleuth/Micrometer tracing) in error responses so that a user's support ticket can be correlated to a log line. Never include stack traces or internal paths in error responses.

413 Before Your Handler
If the multipart size limit is hit at the Nginx or ALB level, the 413 is returned before Spring sees the request. Your @ControllerAdvice only handles limits enforced by Spring/Tomcat. Document this in your runbook.
Production Insight
Add a Micrometer counter file.upload.rejected with tags for rejection reason — spike alerts on this metric catch bot-driven abuse before it becomes a DDoS.
Key Takeaway
Handle MaxUploadSizeExceededException in @ControllerAdvice and return structured errors with trace IDs — never let stack traces reach clients.

Testing File Upload Endpoints

Testing file upload endpoints requires MockMultipartFile in unit tests and @SpringBootTest with TestRestTemplate or MockMvc for integration tests. The trap most developers fall into is testing only with small valid files — in production, the failures happen at the edges: empty files, files at exactly the size limit, files with spoofed MIME types, and concurrent uploads.

For integration tests, use MockMvc.perform(multipart(...)) to build the request. For testing size limit rejection, you need to mock a large MultipartFile — constructing an actual 100 MB file in a test is slow. Instead, mock MultipartFile.getSize() to return a large value and test the validation logic in isolation.

For S3 integration, use Testcontainers with the localstack image. LocalStack runs a real S3-compatible API locally. Wire your S3AsyncClient to point to http://localhost:4566 in the test profile. This tests the actual AWS SDK call, presigned URL generation, and multipart upload logic without hitting real AWS.

Always include a test that verifies the temp directory cleanup: after the upload completes (or fails), assert that no temp files remain in spring.servlet.multipart.location. Spring should clean them up, but custom code that stores the MultipartFile reference across thread boundaries can prevent cleanup and cause disk exhaustion.

Use Testcontainers LocalStack for S3 Tests
Never mock the S3Client in integration tests — you'll miss serialization issues and presigned URL edge cases. Testcontainers LocalStack gives you a real S3-compatible endpoint in CI with zero AWS cost.
Production Insight
Run a weekly chaos test: upload a 1 MB file 1000 times concurrently via k6 and assert no memory leak between run start and run end using Actuator /actuator/metrics/jvm.memory.used.
Key Takeaway
Test with real magic bytes, boundary sizes, concurrent uploads, and Testcontainers LocalStack — happy-path-only tests will miss every production failure mode.

Production Configuration and Observability

Complete production configuration requires tuning at four layers: Spring multipart, Tomcat connector, Kubernetes pod, and AWS infrastructure. Missing any layer creates a mismatch where upload succeeds in dev and fails silently in production.

For Tomcat, set server.tomcat.max-swallow-size=-1 to prevent Tomcat from aborting connections when the client sends more data than expected. Without this, clients uploading large files get a broken pipe even before Spring processes the request.

For observability, instrument every upload with Micrometer: a Timer for upload duration (segmented by file size bucket), a Counter for successes and failures, and a DistributionSummary for file sizes. Set alert thresholds: upload p99 > 30 seconds usually indicates an S3 or network issue; rejection rate > 5% usually indicates a bot attack or a broken client.

For the Kubernetes pod spec: set resources.limits.memory explicitly and run a load test to validate it before setting. If your upload handler is pure streaming, 512 MB per pod should handle dozens of concurrent 100 MB uploads. If anything calls getBytes(), memory is proportional to concurrent upload volume.

Finally, configure S3 bucket policies: block public access, enable versioning, enable server-side encryption with KMS, and set lifecycle rules to move objects to Glacier after 90 days. Enable S3 access logging to an audit bucket — this is required for compliance in most regulated industries.

Set server.tomcat.max-swallow-size=-1
Without this, Tomcat drops the connection when a request body exceeds its expectations, causing clients to see a connection reset rather than a proper 413 error. This makes debugging much harder.
Production Insight
Alert on file.upload.size.bytes p99 crossing 10 MB — most legitimate uploads are small; a spike in large file sizes is often the first sign of data exfiltration or a misconfigured integration partner.
Key Takeaway
Instrument every upload with size, duration, and rejection metrics; tune Tomcat max-swallow-size; mount a proper emptyDir for temp files; and alert on size and latency percentiles.

MultipartResolver Gone? Why Spring Boot Just Works (Until It Doesn't)

The old-guard tutorials love configuring StandardServletMultipartResolver beans. Don't do it. Spring Boot 3.x auto-configures multipart handling the moment it sees spring.servlet.multipart.enabled=true (it defaults to true). The real trap? Your app works fine locally but fails in production with cryptic 'Required request part is missing' errors.

The why: Spring Boot wraps DispatcherServlet with a MultipartConfigElement automatically. But if you manually define a MultipartResolver bean or mess with servlet registrations, you override this auto-configuration. Then MultipartFile parameters become null because no resolver parses the request.

Production rule: Never define @Bean MultipartResolver. Never set multipartConfigElement on DispatcherServlet manually. Let Spring Boot handle it. If you need custom settings, use application.yml properties only. That single mistake took down our staging environment for three hours.

application.ymlYAML
1
2
3
4
5
6
7
8
9
# io.thecodeforge — production config
spring:
  servlet:
    multipart:
      enabled: true          # auto-configured; explicit for clarity
      max-file-size: 10MB    # per-file limit
      max-request-size: 50MB # total request limit (multiple files)
      file-size-threshold: 2MB # size threshold for writing to disk vs memory
      location: /tmp/uploads # temp directory before processing
Production Trap:
If you define a custom @Bean of type MultipartResolver, Spring Boot silently disables its auto-configuration. This killed our file upload endpoint silently — no logs, no errors, just null MultipartFile parameters. Diagnose this by checking if DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME exists in the bean factory.
Key Takeaway
Don't define custom MultipartResolver beans in Spring Boot 3.x. Rely solely on application.yml multipart properties. When in doubt, remove every manual bean configuration and let Spring Boot's auto-configuration own it.

Upload Files With Form Data: The Bad Practice That Leaks Memory

Need to send a file alongside a user ID or description? The obvious approach—sending the form data as separate request parameters—works. Until you hit a 200MB file and realize you serialized a giant JSON payload into memory alongside it.

Better approach: Use @RequestPart instead of @RequestParam. Why? @RequestPart treats each part of the multipart request independently, streaming file content while the JSON part is deserialized separately. @RequestParam with MultipartFile forces the entire request body into memory first.

Here's the pattern: Declare a DTO for non-file fields, and annotate it with @RequestPart. The file stays as MultipartFile. Spring handles the deserialization and streaming separately. This prevents OutOfMemoryErrors when users upload 4K video files with metadata.

Memory tip: Combine this with spring.servlet.multipart.file-size-threshold set to 0 to force all files straight to disk. Production servers with 512MB RAM survived a 2GB upload stress test using this pattern.

UploadController.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// io.thecodeforge — java tutorial
@RestController
@RequestMapping("/api/uploads")
public class UploadController {

    @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public ResponseEntity<UploadResponse> uploadWithMetadata(
        @RequestPart("metadata") UploadMetadata metadata,  // JSON part
        @RequestPart("file") MultipartFile file) {           // file part

        String fileName = file.getOriginalFilename();
        long size = file.getSize();
        String userId = metadata.userId();

        // Streaming write to disk or S3 — never call file.getBytes()
        saveFileToStorage(file.getInputStream(), userId, fileName);

        return ResponseEntity.ok(new UploadResponse(fileName, size, userId));
    }

    record UploadMetadata(String userId, String description) {}
    record UploadResponse(String fileName, long size, String userId) {}
}
Output
POST /api/uploads (multipart/form-data)
part: metadata -> {"userId":"abc123","description":"avatar"}
part: file -> [binary data]
Response: 200 OK
{"fileName":"avatar.png","size":2048576,"userId":"abc123"}
Memory Saver:
Never call file.getBytes() on large uploads. It loads the entire file into heap memory. Always use file.getInputStream() and stream to disk or S3. Combine with file-size-threshold: 0 in config to write directly to temp files, bypassing memory entirely.
Key Takeaway
Use @RequestPart for mixed JSON/file multipart requests. It keeps file streaming and JSON deserialization separate, preventing memory exhaustion. Pair with zero file-size-threshold for production safety.
● Production incidentPOST-MORTEMseverity: high

OOM Crash During Peak Traffic: The getBytes() Trap

Symptom
Pod memory spiked to 4 GB within seconds of peak traffic, triggering OOMKilled. Heap dumps showed hundreds of byte[] objects each between 200–800 MB.
Assumption
The team assumed Spring Boot's multipart configuration handled memory management automatically and that file.getBytes() was the idiomatic approach shown in tutorials.
Root cause
Every upload handler called MultipartFile.getBytes(), which copies the entire file content into a heap byte array. With 10 concurrent 400 MB uploads, the service needed 4 GB of heap just for the raw bytes, before any processing. The JVM was never configured with -Xmx appropriate for this pattern.
Fix
Replaced file.getBytes() with file.getInputStream() and piped it directly to S3AsyncClient.putObject() using AsyncRequestBody.fromInputStream(). Heap usage for the same 10 concurrent uploads dropped from 4 GB to under 200 MB.
Key lesson
  • Never call getBytes() on user-supplied files in production.
  • Always stream.
  • Add a unit test that uploads a 1 GB file and asserts heap usage stays below 256 MB.
Production debug guideSymptom → root cause → fix5 entries
Symptom · 01
413 Request Entity Too Large before reaching Spring
Fix
The limit is being enforced upstream — check your Nginx client_max_body_size, AWS ALB --target-group-attribute idle timeout, or Kubernetes ingress annotation nginx.ingress.kubernetes.io/proxy-body-size. Spring's spring.servlet.multipart.max-file-size is irrelevant until the request reaches Tomcat. Set the Nginx and ingress limits first, then match the Spring limit. Redeploy and retry with curl -F.
Symptom · 02
MaxUploadSizeExceededException thrown by Spring
Fix
The request reached Spring but exceeded spring.servlet.multipart.max-file-size or spring.servlet.multipart.max-request-size. Add a @ControllerAdvice that catches MaxUploadSizeExceededException and returns a 422 with a user-friendly message. Set spring.servlet.multipart.max-file-size=500MB and max-request-size=510MB (request is slightly larger due to multipart boundaries). Verify with curl -F 'file=@500mb.bin' http://localhost:8080/upload.
Symptom · 03
File upload silently truncated — stored file is smaller than uploaded
Fix
A size limit is being hit at the network layer and the connection is being closed mid-stream without a proper 413 response. Capture a HAR file in the browser devtools and look at the response status code and Content-Length. Check ALB access logs for 408 (request timeout) or 413. Also verify the temp directory (spring.servlet.multipart.location) has sufficient space — if the temp write fails, the stream may be truncated silently.
Symptom · 04
IllegalStateException: Cannot call getInputStream() after getBytes()
Fix
The multipart file's input stream was already consumed. MultipartFile wraps a single-read stream. If your service calls getBytes() for validation and then tries to stream to S3, the stream is exhausted. Refactor to read the stream once: pipe it through a DigestInputStream (for checksum), a virus-scan filter, and then to the sink, all in a single pass.
Symptom · 05
Temporary file not found / No such file or directory on Kubernetes
Fix
The default temp location is /tmp, which may be a read-only layer in distroless images. Set spring.servlet.multipart.location=/uploads/tmp and mount an emptyDir volume at /uploads/tmp in your pod spec. Verify with kubectl exec -it <pod> -- ls -la /uploads/tmp.
★ Debug Cheat SheetFast commands to diagnose upload issues in production
413 from reverse proxy
Immediate action
Check upstream size limits before touching Spring config
Commands
curl -v -F 'file=@testfile.bin' https://api.example.com/upload 2>&1 | grep '< HTTP'
kubectl get ingress my-ingress -o yaml | grep proxy-body-size
Fix now
Add annotation nginx.ingress.kubernetes.io/proxy-body-size: 512m to ingress YAML and kubectl apply
OOM / heap spike during upload+
Immediate action
Check for getBytes() calls in upload handlers immediately
Commands
grep -r 'getBytes()' src/main/java --include='*.java'
jcmd <pid> VM.native_memory summary | grep -i heap
Fix now
Replace all file.getBytes() with file.getInputStream() and stream to destination
Temp dir full causing upload failures+
Immediate action
Check disk space on the temp mount
Commands
kubectl exec -it <pod> -- df -h /tmp
kubectl exec -it <pod> -- ls -lah /tmp | head -20
Fix now
Mount a larger emptyDir or set spring.servlet.multipart.location to a PVC-backed path
File Upload Strategy Comparison
StrategyFile SizeHeap UsageThroughputComplexityBest For
MultipartFile.getBytes()< 10 MBHigh (N × size)LowLowInternal tools, prototypes
MultipartFile.getInputStream() → disk10–200 MBLowMediumLowOn-prem, single-node apps
Stream → S3AsyncClient10–500 MBMinimalHighMediumCloud-native services
S3TransferManager (multipart)100 MB – 5 TBMinimalVery HighMediumLarge media, data files
Presigned S3 URL (client direct)> 200 MBNoneVery HighHighUser-facing media uploads

Key takeaways

1
Always stream files using getInputStream()
never call getBytes() on user-supplied files in production
2
Validate file type using magic bytes (Apache Tika), not the client-supplied Content-Type header
3
Configure size limits at all four layers
Spring, Tomcat, Nginx/ingress, and ALB — a mismatch at any layer causes mysterious failures
4
Use S3TransferManager for files over 100 MB and presigned URLs for user-facing uploads over 200 MB
5
Set an S3 lifecycle rule to abort incomplete multipart uploads after 1 day to prevent orphaned-parts storage costs

Common mistakes to avoid

6 patterns
×

Calling file.getBytes() in the upload handler

Symptom
OOMKilled pods under concurrent upload load, heap dumps full of large byte arrays
Fix
Replace with file.getInputStream() and stream directly to the storage sink. Never buffer the whole file in the JVM.
×

Trusting file.getContentType() or file.getOriginalFilename() for type validation

Symptom
Malicious files bypass type checks; disguised executables or polyglots stored in S3
Fix
Use Apache Tika to detect MIME type from magic bytes. Treat Content-Type as a hint only.
×

Not setting max-swallow-size on Tomcat

Symptom
Clients get connection reset errors instead of 413; no useful error message
Fix
Set server.tomcat.max-swallow-size=-1 in application properties
×

Using the original filename as a filesystem path

Symptom
Path traversal vulnerability — filenames like ../../etc/passwd write to arbitrary locations
Fix
Always generate a UUID-based filename server-side. Store original filename in database only.
×

Not cleaning up S3 multipart upload parts on failure

Symptom
Orphaned S3 multipart parts accumulate, causing unexpected storage charges
Fix
Set an S3 lifecycle rule: AbortIncompleteMultipartUpload after 1 day on the upload prefix.
×

Missing Nginx/ingress body size limit increase

Symptom
413 in production that never reproduces locally; works fine in dev without a proxy
Fix
Add nginx.ingress.kubernetes.io/proxy-body-size annotation and set Nginx client_max_body_size to match.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
What happens when you call MultipartFile.getBytes() on a 500 MB file wit...
Q02JUNIOR
How do you configure Spring Boot to accept file uploads up to 200 MB?
Q03JUNIOR
Why is checking file.getContentType() insufficient for security validati...
Q04SENIOR
Describe how you would implement streaming file upload to S3 without buf...
Q05SENIOR
What is the purpose of the spring.servlet.multipart.file-size-threshold ...
Q06SENIOR
How do you safely handle the original filename from a multipart upload i...
Q07SENIOR
Walk me through the architecture for uploading large video files (1–5 GB...
Q08SENIOR
How would you implement a file upload quota system that prevents a singl...
Q01 of 08JUNIOR

What happens when you call MultipartFile.getBytes() on a 500 MB file with 20 concurrent requests?

ANSWER
Each call loads the entire file into a new byte array on the JVM heap. 20 concurrent requests means 20 × 500 MB = 10 GB of heap allocation, almost certainly causing OOMKilled. The fix is to use getInputStream() and stream the bytes to the storage sink without buffering.
FAQ · 6 QUESTIONS

Frequently Asked Questions

01
What is the default maximum file upload size in Spring Boot?
02
How do I return a proper error message when the upload exceeds the size limit?
03
Can I upload multiple files in a single request?
04
How do I test file uploads with curl?
05
Why do uploads work in development but fail in production Kubernetes?
06
How do I get the file download URL after uploading to S3?
🔥

That's Spring Boot. Mark it forged?

9 min read · try the examples if you haven't

Previous
@Async and Async Processing in Spring Boot
20 / 21 · Spring Boot
Next
API Documentation with Swagger/OpenAPI