The AWS SDK for Java v2 makes S3 access straightforward once you’ve seen the right shape for it. This post walks through a minimal Spring Boot configuration, the YAML settings that drive it, a small service with the S3 operations you’ll use most, and an integration test that runs against a real S3 API — without touching AWS — using Testcontainers and LocalStack.
Dependencies
xml
<dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>s3</artifactId> <version>2.x.x</version></dependency>
application.yml
Keep credentials out of the YAML in real deployments — use environment variables, an instance profile, or a secrets manager. The block below shows the settings worth externalizing either way.
yaml
aws: s3: region: eu-central-1 bucket: my-app-bucket endpoint: ${AWS_S3_ENDPOINT:} # only set for LocalStack/MinIO; blank for real AWS path-style-access: false # true for LocalStack/MinIO
A matching @ConfigurationProperties class:
java
@ConfigurationProperties(prefix = "aws.s3")public record S3Properties( String region, String bucket, String endpoint, boolean pathStyleAccess) {}
The S3Client bean
java
@Configuration@EnableConfigurationProperties(S3Properties.class)public class S3Config { @Bean public S3Client s3Client(S3Properties props) { S3ClientBuilder builder = S3Client.builder() .region(Region.of(props.region())) .credentialsProvider(DefaultCredentialsProvider.create()) .serviceConfiguration(b -> b.pathStyleAccessEnabled(props.pathStyleAccess())); if (props.endpoint() != null && !props.endpoint().isBlank()) { builder.endpointOverride(URI.create(props.endpoint())); } return builder.build(); }}
DefaultCredentialsProvider checks environment variables, system properties, the shared ~/.aws/credentials file, and the EC2/ECS instance role, in that order — so the same code works locally and in production without changes.
A minimal S3 service
java
@Servicepublic class S3StorageService { private final S3Client s3Client; private final String bucket; public S3StorageService(S3Client s3Client, S3Properties props) { this.s3Client = s3Client; this.bucket = props.bucket(); } public void upload(String key, byte[] content, String contentType) { s3Client.putObject( PutObjectRequest.builder() .bucket(bucket) .key(key) .contentType(contentType) .build(), RequestBody.fromBytes(content)); } public byte[] download(String key) { return s3Client.getObject( GetObjectRequest.builder().bucket(bucket).key(key).build(), ResponseTransformer.toBytes()).asByteArray(); } public List<String> listKeys(String prefix) { return s3Client.listObjectsV2( ListObjectsV2Request.builder() .bucket(bucket) .prefix(prefix) .build()) .contents() .stream() .map(S3Object::key) .toList(); } public void delete(String key) { s3Client.deleteObject( DeleteObjectRequest.builder().bucket(bucket).key(key).build()); }}
Four methods cover most day-to-day usage: putObject, getObject, listObjectsV2, and deleteObject. Everything else in the SDK is a variation on this same request/response builder pattern.

Hinterlasse einen Kommentar