Getting Started with AWS S3 with Spring Boot as a Complete Beginner — A Developer's Guide.
Introduction
When I first heard "AWS S3" I thought it was some complex cloud technology only senior developers touch. I was wrong.
S3 is actually one of the simplest AWS services to understand and use, In this blog I will explain AWS S3 the i way I wish someone had explained it to me - no jargon, just plain simple English with real code examples.
By the end of this blog you will understand:
What S3 is and why developers use it.
How to set it up on AWS.
How to connect it to a Spring Boot Application
How to upload , download and delete files with actual code.
Let me start from the very beginning.
What is AWS S3?
Imagine you have a pen drive - but instead of it sitting in your pocket, it lives on Amazon's servers and the entire internet can access it (if you allow it).
That is AWS S3 - Simple Storage Service. It is cloud storage. You put files in, you take files out.
Three Terms You Must Know.
Before touching any code, understand these three terms:
Bucket - Think of it as a folder on that pen drive. It has a name , suppose My-Files.
Every file you store goes inside a bucket.
Object - A single file stored inside a bucket. A PDF, an image, a video - anything is an object.
Key - The name or address of that object inside that bucket. If you store resume.pdf , the key is resume.pdf. If you store it inside a subfolder it becomes documents/resume.pdf.
So the full address of any file in S3 is:
bucket-name -> key
My-files -> resume.pdf
Why Not Just Store Files in MySQL?
This is the first question I had. i was already using MySQL - why not just store files there too?
The answer is :-
MySQL is designed for structured data - names , emails , numbers , dates.
Storing binary files (PDFs, images , videos) in MySQL makes it extremely slow.
A 100MB file in your database makes every query slower.
S3 is specifically built for binary storage - cheap , fast , infinitely scalable.
So the pattern most developers follow is :-
MySQL -> stores file metadata (name , size , type , who owns it)
S3 -> stores the actual file content.
Setting Up S3 on AWS:-
Step 1 - Create an AWS Account.
Go to aws.amazon.com and create a free account.
AWS has a free tier - S3 gives you 5GB free storage which is more than enough to learn and build projects.
Step 2 - Create a Bucket.
Go to AWS Console -> search for S3
Click Create Bucket
Give it a Unique name (bucket names are globally unique across all AWS accounts).
Choose a region - pick the one closest to your users.
Keep Block all public access turned ON - your backend application will be only one accessing files , not the public internet.
Click Create Bucket.
Step 3- Create IAM Credentials.
Your backend application needs permission to access S3. You give it permission through IAM (Identity and Access Management).
Go to AWS Console -> Search for IAM
Click Users -> Create User
Give it a name like s3-user.
Click Attach policies directly.
Search for AmazonS3FullAccess -> select it.
Create the user
Go to the user -> Security credentials -> Create Access Key.
Choose Application running outside AWS
Download the CSV - it contains your Access Key Id and Secret Access Key
Important: Save these keys safely. You cannot see the secret key again after this screen.
Adding S3 to your Spring Boot Project.(Since i am working with Spring Boot)
Step 1 - Add Dependency in your pom.xml
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
<version>2.20.26</version>
</dependency>
Step 2 - Add Credentials to application.properties.
aws.access-key=YOUR_ACCESS_KEY_ID
aws.secret-key=YOUR_SECRET_ACCESS_KEY
aws.region=ap-south-1
aws.s3.bucket=your-bucket-name
Note: Never commit these to GitHub. Add application.properties to your .gitignore file. Use environment variables or a secrets manager in production.
Step 3 - Create S3Config.java
This class creates the connection to S3 and make it available as a Spring Bean:
@Configuration
public class S3Config {
@Value("${aws.access-key}")
private String accessKey;
@Value("${aws.secret-key}")
private String secretKey;
@Value("${aws.region}")
private String region;
@Bean
public S3Client s3Client() {
AwsBasicCredentials credentials = AwsBasicCredentials.create(accessKey, secretKey);
return S3Client.builder()
.region(Region.of(region))
.credentialsProvider(StaticCredentialsProvider.create(credentials))
.build();
}
}
Think of S3Client as your remote control for S3. Every operation - upload , download , delete - goes through this object.
@Bean means Spring creates this object once when the app starts and reuses it everywhere. You never create new S3Client() manually.
Uploading a File :
Now the fun part - actual file operations.
The Upload Code
@Service
public class S3Service {
private final S3Client s3Client;
@Value("${aws.s3.bucket}")
private String bucket;
public S3Service(S3Client s3Client) {
this.s3Client = s3Client;
}
public void uploadFile(String fileName, MultipartFile file) {
try {
PutObjectRequest request = PutObjectRequest.builder()
.bucket(bucket)
.key(fileName)
.contentType(file.getContentType())
.contentLength(file.getSize())
.build();
s3Client.putObject(request,
RequestBody.fromInputStream(file.getInputStream(), file.getSize()));
} catch (IOException e) {
throw new RuntimeException("Could not upload file: " + fileName);
}
}
}
What each line does:-
PutObjectRequest - This is just a description of what you want to do. You are telling S3:
Which bucket to put file in
What key (name) to give it
What type of file it is (PDF , image etc)
How big it is
s3Client.putObject(request, RequestBody.fromInputStream(...)) — this is the actual upload. fromInputStream streams the file data directly to S3 without loading the entire file into memory first. This is important for large files.
One Tip — Always Prefix Filename With UUID.
String fileName = UUID.randomUUID() + "_" + originalFileName;
Why? If two users upload a file named resume.pdf, without a UUID prefix they would overwrite each other in S3. A UUID prefix makes every key unique.
Downloading a File
public InputStream downloadFile(String fileName) {
GetObjectRequest request = GetObjectRequest.builder()
.bucket(bucket)
.key(fileName)
.build();
return s3Client.getObject(request);
}
s3Client.getObject() returns an InputStream — think of it as a pipe. Your file's data flows through that pipe byte by byte.
Sending the File to the Browser.
public ResponseEntity<byte[]> downloadFile(String id) throws IOException {
File file = fileRepository.findById(id)
.orElseThrow(() -> new RuntimeException("File not found"));
try (InputStream inputStream = s3Service.downloadFile(file.getFileName())) {
byte[] content = inputStream.readAllBytes();
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(file.getFileType()))
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + file.getOriginalName() + "\"")
.body(content);
}
}
Two Things to Notice Here
try-with-resources — the try (InputStream ...) syntax automatically closes the stream when done, even if an exception occurs. Without this you would have a resource leak — S3 would hold a connection open with nobody on the other end.
Content-Disposition: attachment — this header tells the browser to download the file instead of opening it in a tab. If you use inline instead, the browser opens it in a tab (useful for PDF previews)
Deleting a File
public void deleteFile(String fileName) {
DeleteObjectRequest request = DeleteObjectRequest.builder()
.bucket(bucket)
.key(fileName)
.build();
s3Client.deleteObject(request);
}
The simplest of the three operations. One thing to know — S3 delete is idempotent. Deleting a file that doesn't exist does not throw an error. So you never need to check if the file exists before deleting.
What Else can S3 do?
Now that you know the basics, here are some more powerful S3 features to explore next:
Presigned URLs — instead of your server downloading from S3 and sending to the user, you generate a temporary URL (valid for 15 minutes) and give it directly to the browser. The browser downloads from S3 directly — your server is not in the middle. Much better for performance.
Versioning — keep multiple versions of the same file. Upload resume.pdf three times → S3 keeps all three versions. You can go back to any version anytime.
Lifecycle Rules — automatically delete or move files to cheaper storage after a certain number of days. Useful for cleanup.
Event Notifications — trigger a Lambda function whenever a file is uploaded. Useful for auto-generating thumbnails, virus scanning, text extraction etc.(will implement it later)
Summary
Here is everything in one place:
| Concept | What It Means |
|---|---|
| Bucket | Your storage folder in S3 |
| Object | A single file in that bucket |
| Key | The name/address of the file in the bucket |
| S3Client | Your remote control to talk to S3 |
| PutObjectRequest | Upload a file |
| GetObjectRequest | Download a file |
| DeleteObjectRequest | Delete a file |
| UUID prefix | Prevents filename collisions between users |
| InputStream | A stream of bytes — how S3 gives you file data |
| try-with-resources | Automatically closes InputStream to prevent resource leaks |
Final Thoughts:-
S3 looked intimidating to me before I used it. But once I understood the three core concepts — bucket, object, key — everything else made sense.
The best way to learn is to build something real with it. I built DocVault — a document management system — which is live at docvault.site . The entire file storage runs on S3.
If you are a beginner building your first project with S3, start simple — just upload one file, download it, delete it. Once those three work you understand S3.
Feel free to connect with me on LinkedIn or check out the project on GitHub (https://github.com/harshjha987/DocVault) if you want to see the full implementation.
This blog is written from the perspective of a beginner who just learned S3. If you found it helpful or have any feedback, please leave a comment below.




