S3AsyncUploadService.java
package com.util.s3;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import software.amazon.awssdk.core.async.AsyncRequestBody;
import software.amazon.awssdk.services.s3.S3AsyncClient;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
@Component
public class S3AsyncUploadService {
private final S3AsyncClient s3AsyncClient;
public S3AsyncUploadService(S3AsyncClient s3AsyncClient) {
this.s3AsyncClient = s3AsyncClient;
}
public Mono<Void> uploadFile(String bucketName, String fileName, byte[] fileContent) {
PutObjectRequest putObjectRequest = PutObjectRequest.builder()
.bucket(bucketName)
.key(fileName)
.build();
return Mono.fromFuture(
s3AsyncClient.putObject(
putObjectRequest,
AsyncRequestBody.fromBytes(fileContent)
)
).then();
}
}
S3UploadService.java
package com.util.s3;
import org.springframework.stereotype.Component;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
@Component
public class S3UploadService {
private final S3Client s3Client;
public S3UploadService(S3Client s3Client) {
this.s3Client = s3Client;
}
public void uploadFile(String bucketName, String fileName, byte[] fileContent) {
PutObjectRequest putObjectRequest = PutObjectRequest.builder()
.bucket(bucketName)
.key(fileName)
.build();
s3Client.putObject(putObjectRequest, RequestBody.fromBytes(fileContent));
}
}
S3Resource.java
package com.util.resource;
import com.util.s3.S3AsyncUploadService;
import com.util.s3.S3UploadService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Mono;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.CreateBucketRequest;
import software.amazon.awssdk.services.s3.model.ListObjectsV2Request;
import software.amazon.awssdk.services.s3.model.ListObjectsV2Response;
import software.amazon.awssdk.services.s3.model.S3Object;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.stream.Collectors;
@RestController
@RequestMapping("/s3")
public class S3Resource {
private final S3UploadService s3UploadService;
private final S3AsyncUploadService s3AsyncUploadService;
private final S3Client s3Client;
@Value("${aws.s3.bucket}")
private String bucketName;
public S3Resource(S3UploadService s3UploadService, S3AsyncUploadService s3AsyncUploadService, S3Client s3Client) {
this.s3UploadService = s3UploadService;
this.s3AsyncUploadService = s3AsyncUploadService;
this.s3Client = s3Client;
}
@GetMapping("/upload-string")
public Mono<ResponseEntity<Map<String, Long>>> uploadString(@RequestParam("content") String content) {
s3Client.createBucket(CreateBucketRequest.builder().bucket(bucketName).build());
String syncKey = "sync-uploaded.txt";
String asyncKey = "async-uploaded.txt";
byte[] bytes = content.getBytes(StandardCharsets.UTF_8);
// 1. Synchronous upload
s3UploadService.uploadFile(bucketName, syncKey, bytes);
// 2. Asynchronous upload
return s3AsyncUploadService.uploadFile(bucketName, asyncKey, bytes)
.then(Mono.fromCallable(() -> {
ListObjectsV2Request listReq = ListObjectsV2Request.builder()
.bucket(bucketName)
.build();
ListObjectsV2Response listRes = s3Client.listObjectsV2(listReq);
Map<String, Long> keysWithSize = listRes.contents().stream()
.collect(Collectors.toMap(S3Object::key, S3Object::size));
return ResponseEntity.ok(keysWithSize);
}));
}
}
Problem:
I'm using Java 21 with Spring Boot WebFlux and AWS SDK v2. I have two services to upload files to S3:
- S3UploadService uses the synchronous S3Client — works as expected.
- S3AsyncUploadService uses the asynchronous S3AsyncClient — creates the S3 object (key), but the file has 0 bytes, even though the content is not empty.
Code Overview:
S3UploadService — synchronous, uses RequestBody.fromBytes(...).
S3AsyncUploadService — asynchronous, uses AsyncRequestBody.fromBytes(...).
An endpoint /s3/upload-string uploads a simple string to both services:
- Creates a bucket.
- Uploads one file using the sync service and another using the async service.
- Lists the objects and returns the object sizes.
- The sync-uploaded file has the expected size. The async-uploaded file has size 0.
Observed Behavior:
sync-uploaded.txt contains the expected content.
async-uploaded.txt is present in the bucket, but has 0 bytes.
Expected:
- Both uploads should store the same content (the same byte[] is used).
What I’ve Tried:
Confirmed the input byte array is not empty.
Ensured
.then()is chained after theMono.fromFuture(...).Verified the bucket and object keys are correct.
Question: What might be causing the S3AsyncClient to upload a 0-byte file, even though the content is present?





ListObjectsV2Response listRes = s3Client.listObjectsV2(listReq);public Mono<Void>, and use flatMap() instead of then() in the endpoint.