Skip to content

Add expectContinueThresholdInBytes config to S3#6864

Merged
alextwoods merged 14 commits intomasterfrom
alexwoo/s3_100cont_threshold
Apr 25, 2026
Merged

Add expectContinueThresholdInBytes config to S3#6864
alextwoods merged 14 commits intomasterfrom
alexwoo/s3_100cont_threshold

Conversation

@alextwoods
Copy link
Copy Markdown
Contributor

@alextwoods alextwoods commented Apr 15, 2026

Motivation and Context

The Expect: 100-continue header allows a client to check whether the server will accept a request before sending the body. While this saves bandwidth on rejected requests, it adds a full round-trip of latency on every successful request. Benchmark data shows this overhead can increase latency by up to 30% for small uploads (under 1 MB) on public internet.

Currently the SDK unconditionally adds the header to all PutObject and UploadPart requests (when expectContinueEnabled is true). This change introduces a configurable content-length threshold so the header is skipped for small payloads where the cost outweighs the benefit.

This is consistent with other AWS SDKs — JavaScript V3 and Kotlin both use a content-length threshold for this header.

Related to #6774 and #6828

Modifications

  • Added expectContinueThresholdInBytes configuration with default of 1MB.

Testing

new + existing unit tests.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)

Checklist

  • I have read the CONTRIBUTING document
  • Local run of mvn install succeeds
  • My code follows the code style of this project
  • My change requires a change to the Javadoc documentation
  • I have updated the Javadoc documentation accordingly
  • I have added tests to cover my changes
  • All new and existing tests passed
  • I have added a changelog entry. Adding a new entry must be accomplished by running the scripts/new-change script and following the instructions. Commit the new file created by the script in .changes/next-release with your changes.
  • My change is to implement 1.11 parity feature and I have updated LaunchChangelog

License

  • I confirm that this pull request can be released under the Apache 2 license

@alextwoods alextwoods requested a review from a team as a code owner April 15, 2026 16:32
@alextwoods alextwoods added the api-surface-area-approved-by-team Indicate API surface area introduced by this PR has been approved by team label Apr 16, 2026
Copy link
Copy Markdown
Contributor

@Fred1155 Fred1155 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we modify the Expect100ContinueHeaderTest as well?


private static final String DECODED_CONTENT_LENGTH_HEADER = "x-amz-decoded-content-length";
private static final String CONTENT_LENGTH_HEADER = "Content-Length";
private static final long DEFAULT_EXPECT_CONTINUE_THRESHOLD_IN_BYTES = 1_048_576L;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trying to find a better way to define DEFAULT_EXPECT_CONTINUE_THRESHOLD_IN_BYTES in only one place in case of when changing values, we only changed one of them. But seems difficult.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think it is better to create a new S3configuration using builder.build() and get the default value out of it when needed at line 66 instead of defining another one here?

Copy link
Copy Markdown
Contributor Author

@alextwoods alextwoods Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah good call - I think the right place for the default is in S3Configuration as thats how we do other defaults.

The previous logic was handling the case where S3config isn't available - I'm not sure when that would ever actually happen. I've updated this logic to just default to using 0 when there isn't a config, effectively disabling the threshold.

// -----------------------------------------------------------------------

@Test
void modifyHttpRequest_putObject_contentLengthAboveThreshold_shouldAddExpectHeader() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: can we use parameterized tests?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, good idea! Refactored these to be parameterized.

Comment on lines +109 to +113
if (this.expectContinueThresholdInBytes.value() < 0) {
throw new IllegalArgumentException(
"expectContinueThresholdInBytes must not be negative, but was: "
+ this.expectContinueThresholdInBytes.value());
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forgot to raise this earlier... should we throw error if expectContinueEnabled is false and expectContinueThresholdInBytes is configured? Don't remember how we handle it for mutlipartEnabled vs multipartConfiguration, let me check

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah - good call out - I believe for multipartEnable + multipartConfiguration we do not throw - we just silently ignore the multipartConfiguration.

@alextwoods
Copy link
Copy Markdown
Contributor Author

Should we modify the Expect100ContinueHeaderTest as well?

Yeah, good question. I didn't add anything to these tests since the documentation on them says "Functional tests to verify RFC 9110 compliance". And that behavior hasn't changed. Those tests don't test the pre-existing S3 expectContinueEnabled configuration either, so I didn't update them.

What do you think?

@alextwoods alextwoods enabled auto-merge April 22, 2026 21:28
@alextwoods alextwoods added this pull request to the merge queue Apr 23, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to no response for status checks Apr 23, 2026
@alextwoods alextwoods enabled auto-merge April 23, 2026 18:11
@sonarqubecloud
Copy link
Copy Markdown

@alextwoods alextwoods added this pull request to the merge queue Apr 24, 2026
@davidh44 davidh44 removed this pull request from the merge queue due to a manual request Apr 24, 2026
@davidh44 davidh44 enabled auto-merge April 24, 2026 02:12
@davidh44 davidh44 added this pull request to the merge queue Apr 24, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to no response for status checks Apr 24, 2026
@alextwoods alextwoods added this pull request to the merge queue Apr 24, 2026
@alextwoods alextwoods removed this pull request from the merge queue due to a manual request Apr 24, 2026
@alextwoods alextwoods enabled auto-merge April 25, 2026 14:46
@alextwoods alextwoods added this pull request to the merge queue Apr 25, 2026
Merged via the queue into master with commit eca21f8 Apr 25, 2026
11 of 12 checks passed
@github-actions
Copy link
Copy Markdown

This pull request has been closed and the conversation has been locked. Comments on closed PRs are hard for our team to see. If you need more assistance, please open a new issue that references this one.

@github-actions github-actions Bot locked as resolved and limited conversation to collaborators Apr 25, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

api-surface-area-approved-by-team Indicate API surface area introduced by this PR has been approved by team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants