Intermediate · Build
Object Storage & Presigned URLs
Move files behind S3-compatible object storage, upload through the app or direct presigned PUTs, and keep private objects genuinely private.
Primary source: Spring Boot 3.5 Reference: Uploading Files
Lesson 2 of 2
1d streakThe Architectural Question
Public buckets and long-lived URLs leak every private document to whoever holds the link; hard-coding S3 into services makes local development and testing painful.
What this guide proves
- Abstract storage behind an interface so local and cloud swap freely
- Upload with the SDK or hand clients time-boxed presigned URLs
- Serve downloads through expiring presigned GETs after authorization
AWS SDK v2 S3 Client Integration with MinIO & AWS
In modern cloud architectures, storing binary files on local application container filesystems is an anti-pattern. Container filesystems are ephemeral, non-shared, and limited in disk capacity. Instead, applications store files in S3-compatible object storage (AWS S3, MinIO, Cloudflare R2).
Spring Boot integrates with AWS SDK for Java v2 (S3Client and S3Presigner) with automatic credential provider chains and connection pooling.
package com.example.storage;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
import java.net.URI;
@Configuration(proxyBeanMethods = false)
public class S3StorageConfig {
@Bean
public S3Client s3Client() {
return S3Client.builder()
.region(Region.US_EAST_1)
.credentialsProvider(DefaultCredentialsProvider.create())
.build();
}
@Bean
public S3Presigner s3Presigner() {
return S3Presigner.builder()
.region(Region.US_EAST_1)
.credentialsProvider(DefaultCredentialsProvider.create())
.build();
}
}Direct Browser-to-S3 Uploads via Presigned URLs
Having client web browsers upload 500MB videos directly to your Spring Boot application server saturates backend bandwidth, blocks web threads, and increases cloud egress costs.
The Presigned URL pattern offloads data transfer entirely: (1) Browser asks Spring Boot for an upload token; (2) Spring Boot verifies user permissions and signs a time-limited S3 PUT URL; (3) Browser uploads bytes directly to S3; (4) Browser notifies Spring Boot upon completion.
package com.example.storage;
import org.springframework.stereotype.Service;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
import software.amazon.awssdk.services.s3.presigner.model.PresignedPutObjectRequest;
import java.time.Duration;
@Service
public class PresignedUrlService {
private final S3Presigner s3Presigner;
private static final String BUCKET = "enterprise-documents";
public PresignedUrlService(S3Presigner s3Presigner) {
this.s3Presigner = s3Presigner;
}
public String generateUploadUrl(String documentId, String contentType) {
PutObjectRequest objectRequest = PutObjectRequest.builder()
.bucket(BUCKET)
.key("uploads/" + documentId)
.contentType(contentType)
.build();
// Generate time-limited upload URL valid for 15 minutes
PresignedPutObjectRequest presigned = s3Presigner.presignPutObject(builder -> builder
.signatureDuration(Duration.ofMinutes(15))
.putObjectRequest(objectRequest)
);
return presigned.url().toString();
}
}Bucket Lifecycle Rules & Antivirus Workflows
Cloud storage must be governed by automated lifecycle rules. Transition staging uploads to cheaper Glacier storage after 90 days and permanently expire incomplete multipart uploads after 7 days.
Integrate S3 Event Notifications with an AWS Lambda or asynchronous worker to trigger ClamAV virus scanning on uploaded files before marking them as approved for end-user downloading.
Testing Presigned Uploads and Expiration Enforcement
Test direct presigned upload using curl with HTTP PUT:
# 1. Request presigned URL from Spring Boot
UPLOAD_URL=$(curl -s http://localhost:8080/api/documents/doc-101/upload-url | jq -r .url)
# 2. Upload file directly to S3 using the presigned URL
curl -i -X PUT -T report.pdf -H "Content-Type: application/pdf" "$UPLOAD_URL"
# EXPECTED: HTTP/1.1 200 OK (Uploaded directly to S3 without hitting Spring server!)
# 3. Verify upload expires after 15 minutes
# Attempting upload after expiration returns HTTP 403 Forbidden with RequestExpiredActive Recall Checkpoint
Why does uploading files directly to S3 via presigned URLs improve backend scalability?
Hint: Think about server network bandwidth and thread allocation under concurrent file uploads.
Show answer & explanation
The backend server only generates a cryptographically signed URL string (< 1KB), offloading all multi-megabyte file streaming, bandwidth consumption, and socket connections to AWS S3 infrastructure.
Runtime Architecture & Flow
Spring MVC · runtime visualization
Object Storage & Presigned URLs
Dispatcher
Abstract storage behind an interface so local and cloud swap freely
Binding
Upload with the SDK or hand clients time-boxed presigned URLs
Use case
Serve downloads through expiring presigned GETs after authorization
Response
Swap local attachment storage for MinIO, then issue a 60-second presigned download URL and watch it expire.
Working Implementation
Code Walkthrough
Swap local attachment storage for MinIO, then issue a 60-second presigned download URL and watch it expire.
package com.example.storage;
import org.springframework.stereotype.Service;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
import software.amazon.awssdk.services.s3.presigner.model.PresignedPutObjectRequest;
import java.time.Duration;
@Service
public class PresignedUrlStorageLab {
private final S3Presigner s3Presigner;
public PresignedUrlStorageLab(S3Presigner s3Presigner) {
this.s3Presigner = s3Presigner;
}
public String createUploadUrl(String bucket, String key, String contentType) {
var putRequest = PutObjectRequest.builder()
.bucket(bucket)
.key(key)
.contentType(contentType)
.build();
PresignedPutObjectRequest presigned = s3Presigner.presignPutObject(builder -> builder
.signatureDuration(Duration.ofMinutes(15))
.putObjectRequest(putRequest)
);
return presigned.url().toString();
}
}Synthesis & Production Verification
Deliberate Failure Condition
- Break: Generate presigned URLs valid for seven days and share them in emails
- Observe: Forwarded links keep granting access long after permissions changed
- Repair: Presign narrowly: shortest workable lifetime, issued only after your authorization check, since possession of the URL becomes the credential.
Production Acceptance Criteria
- Storage implementation swaps via configuration alone
- Downloads require app authorization then redirect to a presigned URL
- Expired URLs fail closed after the configured lifetime
Key Takeaway
Direct browser-to-S3 uploads via presigned URLs offload heavy file transfers from application servers, saving JVM memory and network bandwidth.
Architectural & Technical Review
Core Interview Questions
Q01Why are presigned S3 URLs preferred over streaming large files through Spring Boot application servers?Show answer
Direct uploads to S3 eliminate backend network bandwidth consumption and socket thread starvation. The application server only generates a signed URL string (< 1KB), offloading multi-megabyte data transfer to cloud object storage.
Q02How do AWS SDK v2 S3Presigner time-limited signatures enhance upload security?Show answer
Presigned URLs embed cryptographic HMAC signatures with an explicit expiration window (e.g. 15 minutes) and restricted HTTP method/bucket path. Any request attempted after expiration or targeting another path is rejected by AWS.
Q03What automated operational policies should govern production S3 storage buckets?Show answer
Configure S3 Lifecycle Rules to abort incomplete multipart uploads after 7 days, transition older documents to Infrequent Access / Glacier, and trigger asynchronous virus scanning upon new file upload events.
Production Pitfalls
Critical Anti-Patterns
- Pitfall: Streaming large video or archive uploads through backend application containers — Consumes backend network bandwidth and blocks worker threads, degrading API responsiveness.
- Pitfall: Generating presigned URLs with indefinite or excessive expiration durations — Extends the window of vulnerability if a signed URL is intercepted or leaked in browser histories.
Module Certification Quiz
Prove Module Mastery
Comprehensive question set covering all lessons in this module.
Primary Documentation & Specifications
Lesson Primary References
Supplementary Module Resources