Spring Boot + S3 Integration
Using the AWS SDK for Java from a Spring Boot application to upload, download, and generate pre-signed URLs for S3 objects — the correct, IAM-role-based way to grant an application S3 access.
Want a visual for this topic?
Generate a diagram tailored to Spring Boot + S3 Integration — the AI picks whichever visual (architecture, flowchart, ER diagram, etc.) best fits this specific AWS concept.
Sign in to generate a visual →🎓 Learning objectives
- •Configure the AWS SDK for Java (v2) as a Spring bean for S3 access
- •Upload and download objects to/from S3 from application code
- •Explain why an application should use its IAM role rather than embedded access keys for S3 access
- •Generate a pre-signed URL and explain what problem it solves
What is it?
Integrating Spring Boot with S3 means using the AWS SDK for Java (v2) as a Spring-managed bean to upload, download, list, and delete objects in an S3 bucket, authenticating via the application's IAM role (never embedded static credentials) and, for user-facing upload/download flows, generating pre-signed URLs so the actual file transfer happens directly between the client and S3 rather than through the application server.
Why it exists
This pattern exists because uploading/downloading files is one of the most common integration needs for any web application, and doing it naively (routing every file through the application server, or worse, embedding long-lived AWS credentials in application config) creates real cost, scalability, and security problems that IAM-role-based auth and pre-signed URLs both directly address.
Problem it solves
It solves securely and efficiently transferring files between end users, the application, and S3 — removing long-lived credentials from the equation via IAM roles, and removing the application server from the data-transfer path for user-facing uploads/downloads via pre-signed URLs.
Intuition
The recurring principle: let the IAM role do the authenticating (never embed keys), and let S3 do the actual data transfer directly with the end user whenever possible (via pre-signed URLs), keeping the application server itself out of the data path for anything that doesn't strictly need server-side processing.
Analogy
A pre-signed URL is like a one-time, time-limited guest pass to a specific locked room in a building you don't have a key to — you can hand that pass directly to a visitor (the end user's browser) so they can go straight into that one room without ever needing your master key (the application's IAM credentials) or you personally escorting them there (routing the file through the application server).
Technical explanation
The AWS SDK for Java v2's DefaultCredentialsProvider checks a fixed chain of credential sources in order (environment variables, Java system properties, the shared AWS config/credentials file, then the container/instance metadata service) — on ECS/EKS/EC2, it's the metadata-service check that resolves to the task role/IRSA role/instance profile automatically, with no code needing to know which specific compute platform it's running on. A pre-signed URL embeds an AWS Signature Version 4 signature computed from the requesting principal's actual IAM credentials, the requested action/resource, and an expiration timestamp — S3 recomputes and validates that signature against the request at the time it's actually used, meaning the signing principal's own IAM permissions (not some separate 'pre-signed URL permission system') are what ultimately gate whether the pre-signed action succeeds, and revoking the underlying IAM permission before the URL expires will cause it to start failing even though the URL string itself is unchanged.
Architecture
The Spring Boot application's S3Client bean, when running on ECS/EKS/EC2, resolves credentials automatically through the SDK's default credential provider chain — checking the task's/Pod's/instance's assigned IAM role via the container/instance metadata service, with no explicit key configuration needed. For pre-signed URLs, an S3Presigner bean uses the same underlying credentials to cryptographically sign a URL (embedding an expiration timestamp and a signature computed via AWS Signature Version 4) that S3 itself validates directly when the client makes the request — S3 never needs to check back with the application or IAM at request time, the signature alone proves the request was authorized at generation time and hasn't expired.
Workflow
- Add the AWS SDK for Java v2 S3 module dependency. 2) Define an
S3Client(orS3AsyncClient) as a Spring@Bean, letting the SDK's default credential provider chain automatically resolve credentials from the application's IAM role. 3) For direct application-side upload/download, useputObject/getObjectcalls against the client. 4) For user-facing flows, use anS3Presignerto generate a time-limited pre-signed URL for a specific object and HTTP method (GET for download, PUT for upload), and return that URL to the client. 5) Grant the application's IAM role (task role/IRSA role/instance profile) exactly the S3 actions it needs on exactly the bucket/prefix it needs, following least privilege.
Example
A document-management application generates a pre-signed PUT URL for a user's browser to upload a file directly to a private S3 bucket, then, after upload, the application (using its own IAM role, not the pre-signed URL) reads the object's metadata to confirm the upload and records it in the database — the multi-megabyte file bytes themselves never pass through the application's own compute.
Real-world usage
Direct browser-to-S3 upload via pre-signed URLs is the standard pattern for any application handling user file uploads at meaningful scale (profile photos, documents, media) specifically to avoid application-server bandwidth/memory costs, and is commonly paired with an S3 event notification triggering a Lambda function for post-upload processing (thumbnail generation, virus scanning, content moderation) since the application itself no longer sees the raw upload directly.
Trade-offs
Pre-signed URLs remove the application server from the data path, which is great for cost and scalability, but also mean the application loses the ability to inspect/validate the file content as it's uploaded — validation has to happen either client-side (unreliable) or as a follow-up step after upload (e.g., an S3 event triggering a Lambda validation function). Using the SDK's default credential provider chain is simpler and more secure than embedded keys, but requires the application's actual runtime environment (ECS task role, EKS IRSA, EC2 instance profile) to be correctly configured — a local development environment needs a separate credential source (typically an AWS CLI profile).
Visual explanation
Picture two paths into the same locked warehouse (S3). Path one: the application's own staff (its IAM role) walk in directly using their own badge, for routine internal business (server-side reads/writes). Path two: an external visitor (the end user's browser) is handed a temporary, single-use gate pass (the pre-signed URL) that lets them walk straight to one specific loading dock (one object) without ever needing a staff badge or an escort.
Advantages
- —
IAM-role-based authentication means zero long-lived credentials to manage, rotate, or accidentally leak in application config or source control
- —
Pre-signed URLs let large file transfers bypass the application server entirely, reducing server load, memory pressure, and network cost
- —
The AWS SDK v2's default retry policy and connection pooling handle common transient failures without custom application code
- —
Fine-grained IAM policies scoped to specific buckets/prefixes/actions keep the application's S3 access following least privilege
Disadvantages
- —
Pre-signed URLs remove the application's ability to inspect content during upload — server-side validation (virus scanning, content-type verification) has to happen as an asynchronous follow-up step, typically triggered by an S3 event notification
- —
A pre-signed URL, once generated, grants access to anyone who has it until it expires — it can't be revoked early, so expiration times need to be chosen deliberately short for sensitive operations
- —
Local development requires a separate credential source (AWS CLI profile, environment variables) since there's no ECS task role/instance profile to fall back on outside of AWS's own compute
- —
Overly broad IAM policies (granting
s3:*on all buckets, a common shortcut during initial development) undermine the security benefit of role-based auth if never tightened before production
Common mistakes
- —
Embedding a static AWS access key/secret key in
application.propertiesinstead of relying on the IAM role automatically available in ECS/EKS/EC2 - —
Routing large file uploads through the application server (receiving the full file, then re-uploading to S3) instead of using a pre-signed URL for direct browser-to-S3 upload
- —
Setting a pre-signed URL's expiration far longer than actually needed for the use case, unnecessarily widening the window during which a leaked URL could be misused
- —
Granting the application's IAM role overly broad S3 permissions (e.g., access to all buckets) instead of scoping to the specific bucket and prefix the application actually needs
In the AWS Console
- 1
IAM → Policies → Create policy, then attach to the relevant role
Create an IAM policy scoped to the specific S3 bucket/prefix the application needs, and attach it to the application's execution role (ECS task role, EKS IRSA role, or EC2 instance profile).
- 2
S3 → Buckets → [bucket] → Properties → Event notifications → Create event notification
Enable S3 event notifications on the bucket to trigger a Lambda function for post-upload processing.
- 3
CloudTrail → Trails → [trail] → Data events configuration (requires S3 data-event logging enabled)
Review CloudTrail data events for the bucket to audit exactly which requests are being made via generated pre-signed URLs.
🎤 Interview questions
How should a Spring Boot application running on ECS/EKS/EC2 authenticate to S3, and why is this different from providing an access key/secret key in configuration? (Listen for: it should use the credentials automatically available through its IAM role (the ECS task role, EKS Pod's IRSA role, or EC2 instance profile) via the AWS SDK's default credential provider chain, which the SDK picks up with zero explicit configuration; embedding a static access key/secret key in application config is a long-lived credential that can leak and doesn't rotate, which the IAM-role approach avoids entirely)
What is a pre-signed URL, and what problem does it solve? (Listen for: a temporary, signed URL that grants time-limited access to a specific S3 object/action without requiring the requester to have any AWS credentials at all — solves the problem of letting an end user (e.g., a browser) upload or download directly to/from a private S3 bucket without routing the actual file bytes through the application server, and without making the bucket itself public)
Why would a Spring Boot application generate a pre-signed URL for a browser to upload directly to S3, instead of the browser uploading to the application, which then uploads to S3? (Listen for: direct browser-to-S3 upload avoids doubling the file's network transfer (once to the app, once from the app to S3) and avoids tying up application server resources/memory buffering large file uploads — the application only needs to generate the pre-signed URL, a cheap operation, while S3 handles the actual heavy data transfer)
What's the difference between the AWS SDK for Java v1 and v2, and which should a new Spring Boot project use? (Listen for: v2 is the current, actively developed SDK with a more modern, fluent builder-based API, built-in support for non-blocking async clients, and better default credential/region resolution; v1 is legacy and in maintenance mode — new projects should use v2, typically via the software.amazon.awssdk Maven/Gradle coordinates rather than the older com.amazonaws ones)
How would you configure a Spring Boot application to automatically retry a transient S3 request failure? (Listen for: the AWS SDK for Java v2's S3Client already includes a configurable default retry policy handling transient errors (throttling, network blips) with exponential backoff out of the box — the application typically doesn't need custom retry logic for common transient failures, just correct handling of genuinely non-retryable errors like access-denied or not-found)