AWS S3 for Developers — Upload & Download Files with Java (AWS SDK v2)


📘 What is Amazon S3?
Amazon S3 (Simple Storage Service) is a highly scalable, durable, and secure object storage service provided by AWS. It's designed to store and retrieve any amount of data from anywhere on the internet.
S3 is widely used for:
Static website hosting
Backup and restore
Storing media (images, videos, etc.)
Application logs
Data lakes
✅ Advantages of S3
Highly Durable (99.999999999%): S3 replicates data across multiple facilities.
Scalable: No need to worry about provisioning or scaling.
Secure: Supports fine-grained access control via IAM, encryption at rest and in transit.
Cost-effective: Pay for what you use.
Easy integration with SDKs in multiple languages (including Java).
⚠️ Disadvantages of S3
Latency: Not suitable for real-time processing of large volumes.
Eventually consistent for overwrite PUTs and DELETEs.
Costs can scale quickly with frequent access and egress data.
🧠 What will we build?
In this post, we’ll create two examples using AWS SDK v2 for Java:
Simple Example: Upload and download a file from an S3 bucket.
Advanced Example: An S3 service class with fallback handling and custom metadata for secure document management.
⚙️ Project Setup (for both examples)
1. Add AWS SDK v2 to your pom.xml
(Maven)
<dependencyManagement>
<dependencies>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>bom</artifactId>
<version>2.25.2</version> <!-- Use latest stable version -->
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
</dependency>
</dependencies>
💡 Also make sure you have your AWS credentials configured via environment variables, credentials file, or an IAM role (if running on EC2 or ECS).
🟢 Example 1: Simple Upload and Download
🛠 Functional Goal
We want to:
Upload a local file to a specific bucket in S3.
Download a file from the same bucket and save it locally.
This is ideal for learning and setting up the basic SDK operations.
📄 Code
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.model.*;
import java.io.File;
import java.nio.file.Paths;
public class SimpleS3Example {
private static final String BUCKET_NAME = "my-bucket-name";
private static final String FILE_PATH = "files/sample.txt";
private static final String OBJECT_KEY = "uploaded/sample.txt";
public static void main(String[] args) {
Region region = Region.US_EAST_1;
S3Client s3 = S3Client.builder()
.region(region)
.credentialsProvider(DefaultCredentialsProvider.create())
.build();
uploadFile(s3);
downloadFile(s3);
}
private static void uploadFile(S3Client s3) {
PutObjectRequest putRequest = PutObjectRequest.builder()
.bucket(BUCKET_NAME)
.key(OBJECT_KEY)
.build();
s3.putObject(putRequest, Paths.get(FILE_PATH));
System.out.println("✅ File uploaded to S3.");
}
private static void downloadFile(S3Client s3) {
GetObjectRequest getRequest = GetObjectRequest.builder()
.bucket(BUCKET_NAME)
.key(OBJECT_KEY)
.build();
s3.getObject(getRequest, Paths.get("files/downloaded.txt"));
System.out.println("✅ File downloaded from S3.");
}
}
🔵 Example 2: Advanced S3Service Class with Metadata and Fallback
🛠 Functional Goal
We’ll implement a robust S3Service that:
Uploads a document with metadata (like content type).
Downloads a file safely with error handling.
Provides fallback logging for failed operations.
This is useful for production systems dealing with user uploads (resumes, reports, invoices, etc.).
📄 S3Service.java
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.*;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
public class S3Service {
private final S3Client s3Client;
private final String bucket;
public S3Service(S3Client s3Client, String bucket) {
this.s3Client = s3Client;
this.bucket = bucket;
}
/**
* Uploads a file to S3 with optional metadata.
*/
public void upload(Path filePath, String key, Map<String, String> metadata) {
try {
PutObjectRequest request = PutObjectRequest.builder()
.bucket(bucket)
.key(key)
.contentType(Files.probeContentType(filePath))
.metadata(metadata)
.build();
s3Client.putObject(request, RequestBody.fromFile(filePath));
System.out.println("✅ File uploaded with metadata: " + metadata);
} catch (IOException e) {
System.err.println("❌ Failed to read file metadata: " + e.getMessage());
} catch (S3Exception e) {
System.err.println("❌ Failed to upload to S3: " + e.awsErrorDetails().errorMessage());
}
}
/**
* Downloads a file from S3, handles errors and logs failure gracefully.
*/
public void download(String key, Path destination) {
try {
GetObjectRequest request = GetObjectRequest.builder()
.bucket(bucket)
.key(key)
.build();
s3Client.getObject(request, destination);
System.out.println("✅ File downloaded to: " + destination);
} catch (S3Exception e) {
System.err.println("❌ Download failed: " + e.awsErrorDetails().errorMessage());
}
}
}
📄 Usage
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;
import java.nio.file.Path;
import java.util.Map;
public class S3App {
public static void main(String[] args) {
S3Client client = S3Client.builder()
.region(Region.US_EAST_1)
.credentialsProvider(DefaultCredentialsProvider.create())
.build();
S3Service service = new S3Service(client, "my-secure-bucket");
// Upload with metadata
service.upload(
Path.of("docs/report.pdf"),
"documents/report.pdf",
Map.of("uploaded-by", "user-service", "document-type", "report")
);
// Download with fallback
service.download("documents/report.pdf", Path.of("downloads/report_copy.pdf"));
}
}
🏷️ What is Metadata in S3?
Metadata in Amazon S3 refers to key-value pairs that describe an object. There are two types:
System-defined metadata: Includes predefined headers like
Content-Type
,Content-Length
,Last-Modified
, etc. These are mostly set automatically or via specific fields in the SDK (e.g..contentType(...)
).User-defined metadata: Custom attributes that you define with your object. For example:
"uploaded-by"
,"document-type"
,"department"
.
✅ When to use metadata?
User-defined metadata is useful when:
You want to store additional context about a file (e.g., who uploaded it, why, from where).
You want to filter or manage objects later based on those tags.
You're integrating with services that read metadata for processing (e.g., Lambda triggers or Glue jobs).
Example:
Map.of("uploaded-by", "finance-api", "document-type", "invoice")
This metadata will be stored alongside the file in S3 and can be retrieved later.
🛡️ Improving the Fallback Mechanism
The current implementation only prints an error message:
System.err.println("❌ Download failed: " + e.awsErrorDetails().errorMessage());
A better and more robust fallback strategy might include:
Logging to an external monitoring system (e.g., Datadog, CloudWatch).
Retrying the operation (with exponential backoff).
Storing a message in a dead-letter queue (SQS) for future processing.
Returning a custom exception to the service layer for user feedback.
🔁 Example with fallback and retry strategy
Let’s improve the download
method with:
Retry logic
Custom exception
Optional logging placeholder
import java.nio.file.Path;
import java.time.Duration;
public void download(String key, Path destination) {
int maxRetries = 3;
int attempt = 0;
while (attempt < maxRetries) {
try {
GetObjectRequest request = GetObjectRequest.builder()
.bucket(bucket)
.key(key)
.build();
s3Client.getObject(request, destination);
System.out.println("✅ File downloaded to: " + destination);
return;
} catch (S3Exception e) {
attempt++;
System.err.println("⚠️ Attempt " + attempt + " failed: " + e.awsErrorDetails().errorMessage());
if (attempt >= maxRetries) {
// Placeholder: Send to monitoring/logging queue
System.err.println("❌ All retries failed. Logging to fallback system...");
throw new RuntimeException("S3 download failed after retries", e);
}
try {
Thread.sleep(Duration.ofSeconds(2 * attempt).toMillis()); // Exponential backoff
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
}
}
You can further abstract this logic using libraries like Resilience4j if you're in a Spring-based system.
📌 Summary
Amazon S3 is a core AWS service for object storage.
With AWS SDK v2, Java developers can easily integrate upload/download features.
This post showed a simple intro and a robust implementation that reflects real-world use.
Subscribe to my newsletter
Read articles from André Felipe Costa Bento directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

André Felipe Costa Bento
André Felipe Costa Bento
Fullstack Software Engineer.