An exception "The Content-MD5 you specified did not match what we received"

Viewed 22735

I got an exception, I never got before when testing my application that uploads a file from ec2 to s3. The content is:

Exception in thread "Thread-1" com.amazonaws.services.s3.model.AmazonS3Exception: The Content-MD5 you specified did not match what we received. (Service: Amazon S3; Status Code: 400; Error Code: BadDigest; Request ID: 972CB8E04388AB20), S3 Extended Request ID: T7bmFnQ2RlGWlJD+aGYfTy97XZw88pbQrwNB8YCezSjyq6O2joxHRP/6ko+Q2zZeGewkw4x/90k=
    at com.amazonaws.http.AmazonHttpClient.handleErrorResponse(AmazonHttpClient.java:1383)
    at com.amazonaws.http.AmazonHttpClient.executeOneRequest(AmazonHttpClient.java:902)
    at com.amazonaws.http.AmazonHttpClient.executeHelper(AmazonHttpClient.java:607)
    at com.amazonaws.http.AmazonHttpClient.doExecute(AmazonHttpClient.java:376)
    at com.amazonaws.http.AmazonHttpClient.executeWithTimer(AmazonHttpClient.java:338)
    at com.amazonaws.http.AmazonHttpClient.execute(AmazonHttpClient.java:287)
    at com.amazonaws.services.s3.AmazonS3Client.invoke(AmazonS3Client.java:3676)
    at com.amazonaws.services.s3.AmazonS3Client.putObject(AmazonS3Client.java:1439)
    at com.amazonaws.services.s3.transfer.internal.UploadCallable.uploadInOneChunk(UploadCallable.java:131)
    at com.amazonaws.services.s3.transfer.internal.UploadCallable.call(UploadCallable.java:123)
    at com.amazonaws.services.s3.transfer.internal.UploadMonitor.call(UploadMonitor.java:139)
    at com.amazonaws.services.s3.transfer.internal.UploadMonitor.call(UploadMonitor.java:47)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)

What can I do to fix this bug? I used the same code as before in my application.

6 Answers

FWIW, I've managed to find a completely different way of triggering this problem, which requires a different solution.

It turns out that if you decide to assign ObjectMetadata to a PutObjectRequest explicitly, for example to specify a cacheControl setting, or a contentType, then the AWS SDK mutates the ObjectMetadata instance to stash the MD5 that it computes for the put request. This means that if you are putting multiple objects, all of which you think should have the same metadata assigned to them, you still need to create a new ObjectMetadata instance for each and every PutObjectRequest. If you don't do this, then it reuses the MD5 computed from the previous put request and you get the MD5 mismatch error on the second object you try to put.

So, to be explicit, doing something like this will fail on the second iteration:

ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentType("text/html");
for(Put obj: thingsToPut)
{
    PutObjectRequest por = 
        new PutObjectRequest(bucketName, obj.s3Key, obj.file);
    por = por.withMetadata(metadata);
    PutObjectResult res = s3.putObject(por);
}

You need to do it like this:

for(Put obj: thingsToPut)
{
    ObjectMetadata metadata = new ObjectMetadata(); // <<-- New ObjectMetadata every time!
    metadata.setContentType("text/html");
    PutObjectRequest por =
        new PutObjectRequest(bucketName, obj.s3Key, obj.file);
    por = por.withMetadata(metadata);
    PutObjectResult res = s3.putObject(por);
}

I too ran into this problem. How I solved this:

I have a microservice that processes AWS SQS Messages. Each message would create multiple temporary files that would have to be uploaded to S3.

The issue was that the temporary files were named with fixed names without any salt added to them.

So between two messages, it was possible to rewrite the original file that was to be uploaded.

I fixed it by adding a random salt (this can be a UUID or the current time in millis depending on what you want) to the file names, after which the files were not being over-written and were successfully uploaded to S3.

For me it was that I used ContentLength in the params while executing upload. When it is commented out, it worked just fine.

const params = {
  Bucket: "",
  ContentType: "application/json",
  Key: "filename.json",
  // ContentLength: body.length,   <--- what I have commented out
  Body: body
};
await s3.upload(params).promise();
Related