From 5b4dbac6708c6d4623f5afd47d63b760039f48af Mon Sep 17 00:00:00 2001 From: Zoe Wang <33073555+zoewangg@users.noreply.github.com> Date: Tue, 28 Jul 2026 15:58:26 -0700 Subject: [PATCH 1/5] feat: add @SdkAdvancedApi annotation for error-prone APIs Introduce @SdkAdvancedApi in core/annotations to mark APIs that are error-prone to implement, override, call, or configure: misuse compiles cleanly but can fail or misbehave at runtime rather than reporting a clear error. The annotation carries structured, machine-readable guidance (caution kind, contract explanation, safer alternative, and a documentation link) with CLASS retention so build-time or static-analysis tooling can consume it, matching the existing @NotThreadSafe/@Immutable markers. Apply it to the streaming and interceptor extension points identified as error-prone: AsyncRequestBody (type + fromPublisher), AsyncResponseTransformer (type + toPublisher overloads), ResponseTransformer, ContentStreamProvider, the mutating ExecutionInterceptor content hooks (modifyHttpContent, modifyAsyncHttpContent), and the FUTURE_COMPLETION_EXECUTOR advanced client option. This is an advisory marker only: it does not gate compilation and adds no runtime behavior, so it is source and binary compatible. --- .../feature-AWSSDKforJavav2-6933447.json | 6 ++ .../awssdk/annotations/SdkAdvancedApi.java | 76 +++++++++++++++++++ .../awssdk/core/async/AsyncRequestBody.java | 21 +++++ .../core/async/AsyncResponseTransformer.java | 28 +++++++ .../config/SdkAdvancedAsyncClientOption.java | 9 +++ .../interceptor/ExecutionInterceptor.java | 23 ++++++ .../awssdk/core/sync/ResponseTransformer.java | 8 ++ .../awssdk/http/ContentStreamProvider.java | 9 +++ 8 files changed, 180 insertions(+) create mode 100644 .changes/next-release/feature-AWSSDKforJavav2-6933447.json create mode 100644 core/annotations/src/main/java/software/amazon/awssdk/annotations/SdkAdvancedApi.java diff --git a/.changes/next-release/feature-AWSSDKforJavav2-6933447.json b/.changes/next-release/feature-AWSSDKforJavav2-6933447.json new file mode 100644 index 000000000000..adab20bdb298 --- /dev/null +++ b/.changes/next-release/feature-AWSSDKforJavav2-6933447.json @@ -0,0 +1,6 @@ +{ + "type": "feature", + "category": "AWS SDK for Java v2", + "contributor": "", + "description": "Add the `@SdkAdvancedApi` annotation, which marks APIs that are error-prone to implement, override, call, or configure so that using them incorrectly compiles cleanly but can fail or misbehave at runtime. The annotation records structured guidance (the risky usage kind, an explanation of the contract to uphold, a safer alternative, and a documentation link) and is applied to several streaming and interceptor extension points, including AsyncRequestBody, AsyncResponseTransformer, ResponseTransformer, ContentStreamProvider, the mutating ExecutionInterceptor content hooks, and the FUTURE_COMPLETION_EXECUTOR advanced client option." +} diff --git a/core/annotations/src/main/java/software/amazon/awssdk/annotations/SdkAdvancedApi.java b/core/annotations/src/main/java/software/amazon/awssdk/annotations/SdkAdvancedApi.java new file mode 100644 index 000000000000..2da9154ecaa1 --- /dev/null +++ b/core/annotations/src/main/java/software/amazon/awssdk/annotations/SdkAdvancedApi.java @@ -0,0 +1,76 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks an API that is error-prone to use: implementing, overriding, or calling it incorrectly, or + * configuring it with an unsafe value, compiles cleanly but can fail or misbehave at runtime rather + * than reporting a clear error. + * + *

The annotation records the risk information on the API in a structured form: {@link #caution()} + * classifies which kind of use is error-prone, {@link #guidance()} explains the contract that must + * be upheld, {@link #saferAlternative()} points to a lower-risk approach, and {@link #link()} points + * to further documentation. + * + *

This is an advisory marker: it does not gate compilation and imposes no runtime behavior. + */ +@Documented +@Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.FIELD}) +@Retention(RetentionPolicy.CLASS) +@SdkProtectedApi +public @interface SdkAdvancedApi { + + /** + * Which kind of use of this API is error-prone: WHEN_IMPLEMENTED if the risk is in implementing + * or extending the annotated type, WHEN_OVERRIDDEN if it is in overriding the annotated method, + * WHEN_CONFIGURED if it is in setting the annotated field or option, and WHEN_CALLED if it is in + * calling the annotated method (for example a factory that accepts an object you supply and does + * not shield you from that object's contract). Required. + */ + Caution caution(); + + /** + * Explains why this API is error-prone and what you must uphold to use it safely: the parts of + * the contract that are easy to get wrong and the failure that results if they are not met. + */ + String guidance(); + + /** + * An optional pointer to the recommended safer approach that satisfies the same need + * without the risk (for example, a factory method that implements the contract + * correctly). Empty when there is no direct alternative. + */ + String saferAlternative() default ""; + + /** + * An optional link to documentation explaining the risk and correct usage in more + * depth. Empty when there is no dedicated page. + */ + String link() default ""; + + enum Caution { + WHEN_IMPLEMENTED, + WHEN_OVERRIDDEN, + WHEN_CONFIGURED, + WHEN_CALLED + } +} diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/async/AsyncRequestBody.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/async/AsyncRequestBody.java index 36a2d7eb647c..fba1e50b0838 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/async/AsyncRequestBody.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/async/AsyncRequestBody.java @@ -29,6 +29,8 @@ import java.util.function.Consumer; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; +import software.amazon.awssdk.annotations.SdkAdvancedApi; +import software.amazon.awssdk.annotations.SdkAdvancedApi.Caution; import software.amazon.awssdk.annotations.SdkProtectedApi; import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.core.FileRequestBodyConfiguration; @@ -63,6 +65,15 @@ * @see ByteBuffersAsyncRequestBody */ @SdkPublicApi +@SdkAdvancedApi( + caution = Caution.WHEN_IMPLEMENTED, + guidance = "This is a reactive-streams Publisher and must obey the reactive-streams specification. " + + "The SDK re-subscribes on each retry attempt, " + + "so to support retries, reproduce the full content on every subscribe; a body that cannot should fail the " + + "subscriber on a later subscribe rather than emit partial content.", + saferAlternative = "Prefer the AsyncRequestBody.fromFile/fromBytes/fromInputStream factories. If you must supply a " + + "custom Publisher, build it with an established reactive-streams library such as RxJava or Reactor rather " + + "than implementing Publisher by hand.") public interface AsyncRequestBody extends SdkPublisher { /** @@ -94,6 +105,16 @@ default String body() { * @param publisher Publisher of source data * @return Implementation of {@link AsyncRequestBody} that produces data send by the publisher */ + @SdkAdvancedApi( + caution = Caution.WHEN_CALLED, + guidance = "The returned AsyncRequestBody passes each subscribe straight through to the supplied publisher, so " + + "the publisher must itself meet the AsyncRequestBody contract: obey the reactive-streams " + + "specification, and since the SDK re-subscribes on each " + + "retry attempt, reproduce the full content on every subscribe to support retries; a publisher that " + + "cannot should fail the subscriber on a later subscribe rather than emit partial content.", + saferAlternative = "Prefer the AsyncRequestBody.fromFile/fromBytes/fromInputStream factories, which do not " + + "require the caller to supply a contract-compliant publisher. If you must supply one, build it with an " + + "established reactive-streams library such as RxJava or Reactor rather than by hand.") static AsyncRequestBody fromPublisher(Publisher publisher) { return new AsyncRequestBody() { diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/async/AsyncResponseTransformer.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/async/AsyncResponseTransformer.java index dd4a5140170f..14a82b4760a7 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/async/AsyncResponseTransformer.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/async/AsyncResponseTransformer.java @@ -23,6 +23,8 @@ import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; +import software.amazon.awssdk.annotations.SdkAdvancedApi; +import software.amazon.awssdk.annotations.SdkAdvancedApi.Caution; import software.amazon.awssdk.annotations.SdkProtectedApi; import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.core.FileTransformerConfiguration; @@ -86,6 +88,16 @@ * @param Type this response handler produces. I.E. the type you are transforming the response into. */ @SdkPublicApi +@SdkAdvancedApi( + caution = Caution.WHEN_IMPLEMENTED, + guidance = "prepare() is called on each request attempt; if the CompletableFuture it returned on a previous " + + "attempt has already completed, it must return a new instance so the result of a retry is not lost, and " + + "exceptionOccurred() should release any resources the attempt opened (for example an open file channel) " + + "so retries do not leak them. onStream() receives the response body as a reactive-streams Publisher that " + + "the implementation subscribes to, and that subscriber must comply with the reactive-streams " + + "specification; a subscriber that never " + + "requests data stalls the response.", + saferAlternative = "Prefer the AsyncResponseTransformer.toFile/toBytes/toBlockingInputStream factories.") public interface AsyncResponseTransformer { /** * Initial call to enable any setup required before the response is handled. @@ -304,6 +316,14 @@ static AsyncResponseTransformer> * @param Pojo response type. * @return AsyncResponseTransformer instance. */ + @SdkAdvancedApi( + caution = Caution.WHEN_CALLED, + guidance = "The returned ResponsePublisher is a reactive-streams Publisher you must subscribe to and drive: " + + "your subscriber must obey the reactive-streams specification and honor back-pressure. A subscriber " + + "that never requests data stalls the response, and requesting unbounded data can exhaust memory.", + saferAlternative = "Prefer the fully-managed AsyncResponseTransformer.toFile/toBytes factories, which do not " + + "hand you a publisher to drive. If you do consume the publisher, use an established reactive-streams " + + "library such as RxJava or Reactor rather than a hand-written Subscriber.") static AsyncResponseTransformer> toPublisher() { return new PublisherAsyncResponseTransformer<>(); } @@ -331,6 +351,14 @@ static AsyncResponseTransformer AsyncResponseTransformer> toPublisher(Duration timeout) { return new PublisherAsyncResponseTransformer<>(timeout); diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/config/SdkAdvancedAsyncClientOption.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/config/SdkAdvancedAsyncClientOption.java index e37ba953211d..4c15c3c28804 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/config/SdkAdvancedAsyncClientOption.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/config/SdkAdvancedAsyncClientOption.java @@ -18,6 +18,8 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import java.util.concurrent.ThreadPoolExecutor; +import software.amazon.awssdk.annotations.SdkAdvancedApi; +import software.amazon.awssdk.annotations.SdkAdvancedApi.Caution; import software.amazon.awssdk.annotations.SdkPublicApi; /** @@ -52,6 +54,13 @@ public final class SdkAdvancedAsyncClientOption extends ClientOption { * within the I/O thread because it may block the I/O thread and cause deadlock, especially if you are sending * another SDK request in the {@link CompletableFuture} chain since the SDK may perform blocking calls in some cases. */ + @SdkAdvancedApi( + caution = Caution.WHEN_CONFIGURED, + guidance = "An executor that runs on the client's I/O threads (e.g. Runnable::run) can block " + + "them and deadlock the client, especially when the future chain issues another " + + "SDK request. If you set a custom executor, verify it under your own load tests, " + + "including request chains that issue further SDK calls.", + saferAlternative = "Leave unset to use the SDK-managed per-client ThreadPoolExecutor.") public static final SdkAdvancedAsyncClientOption FUTURE_COMPLETION_EXECUTOR = new SdkAdvancedAsyncClientOption<>(Executor.class); diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/interceptor/ExecutionInterceptor.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/interceptor/ExecutionInterceptor.java index 7ade08249da2..0789d8d2053e 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/interceptor/ExecutionInterceptor.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/interceptor/ExecutionInterceptor.java @@ -19,6 +19,8 @@ import java.nio.ByteBuffer; import java.util.Optional; import org.reactivestreams.Publisher; +import software.amazon.awssdk.annotations.SdkAdvancedApi; +import software.amazon.awssdk.annotations.SdkAdvancedApi.Caution; import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.core.SdkRequest; import software.amazon.awssdk.core.SdkResponse; @@ -183,11 +185,32 @@ default SdkHttpRequest modifyHttpRequest(Context.ModifyHttpRequest context, return context.httpRequest(); } + @SdkAdvancedApi( + caution = Caution.WHEN_OVERRIDDEN, + guidance = "Replacing the request body from an interceptor is discouraged: the returned RequestBody becomes the " + + "content the SDK sends, and the request's Content-Length and content hash or checksum are derived from " + + "it, so a body whose content or length does not match what the operation expects corrupts the request " + + "or is rejected by the service. Return the body reported by context.requestBody() unless you have a " + + "specific reason to replace it.", + saferAlternative = "Return context.requestBody() unchanged. If you must supply a different body, build it with " + + "the RequestBody factories rather than a hand-written ContentStreamProvider.") default Optional modifyHttpContent(Context.ModifyHttpRequest context, ExecutionAttributes executionAttributes) { return context.requestBody(); } + @SdkAdvancedApi( + caution = Caution.WHEN_OVERRIDDEN, + guidance = "Replacing the async request body from an interceptor is discouraged: the returned AsyncRequestBody " + + "becomes the content the SDK sends, and the request's Content-Length and content hash or checksum are " + + "derived from it, so a body whose content or length does not match what the operation expects corrupts " + + "the request or is rejected by the service. It must also comply with the reactive-streams " + + "specification; the SDK re-subscribes to it " + + "on each retry attempt, so to support retries it must reproduce the full content on every subscribe. " + + "Return the body reported by context.asyncRequestBody() unless you have a specific reason to replace " + + "it.", + saferAlternative = "Return context.asyncRequestBody() unchanged. If you must supply a different body, build it " + + "with the AsyncRequestBody factories or an established reactive-streams library.") default Optional modifyAsyncHttpContent(Context.ModifyHttpRequest context, ExecutionAttributes executionAttributes) { return context.asyncRequestBody(); diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/sync/ResponseTransformer.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/sync/ResponseTransformer.java index 279c29c29c3c..0ab24f5c119a 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/sync/ResponseTransformer.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/sync/ResponseTransformer.java @@ -28,6 +28,8 @@ import java.nio.file.Path; import java.time.Duration; import java.util.Map; +import software.amazon.awssdk.annotations.SdkAdvancedApi; +import software.amazon.awssdk.annotations.SdkAdvancedApi.Caution; import software.amazon.awssdk.annotations.SdkProtectedApi; import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.core.ResponseBytes; @@ -75,6 +77,12 @@ */ @FunctionalInterface @SdkPublicApi +@SdkAdvancedApi( + caution = Caution.WHEN_IMPLEMENTED, + guidance = "transform() must fully drain and close the response stream and honor thread interrupts. A " + + "transformer that blocks or returns without consuming the stream hangs the calling thread and leaks " + + "the underlying HTTP connection.", + saferAlternative = "Prefer the ResponseTransformer.toFile/toBytes/toOutputStream/toInputStream factories.") public interface ResponseTransformer { /** * Process the response contents. diff --git a/http-client-spi/src/main/java/software/amazon/awssdk/http/ContentStreamProvider.java b/http-client-spi/src/main/java/software/amazon/awssdk/http/ContentStreamProvider.java index 4ae96838d8b4..a129c42bd3f6 100644 --- a/http-client-spi/src/main/java/software/amazon/awssdk/http/ContentStreamProvider.java +++ b/http-client-spi/src/main/java/software/amazon/awssdk/http/ContentStreamProvider.java @@ -24,6 +24,8 @@ import java.util.Arrays; import java.util.Map; import java.util.function.Supplier; +import software.amazon.awssdk.annotations.SdkAdvancedApi; +import software.amazon.awssdk.annotations.SdkAdvancedApi.Caution; import software.amazon.awssdk.annotations.SdkProtectedApi; import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.utils.IoUtils; @@ -41,6 +43,13 @@ */ @SdkPublicApi @FunctionalInterface +@SdkAdvancedApi( + caution = Caution.WHEN_IMPLEMENTED, + guidance = "newStream() must return a stream positioned at the start of the content on every call, closing any " + + "previous stream it opened. A stale or unreset stream corrupts the request body and breaks retries, " + + "which re-read the content.", + saferAlternative = "Prefer the ContentStreamProvider.fromInputStream/fromByteArray/fromString factories, which " + + "handle reset correctly.") public interface ContentStreamProvider { /** * Create {@link ContentStreamProvider} from a byte array. This will copy the contents of the byte array. From 294214179c3331540e9775032ad9dd7dbea9ccef Mon Sep 17 00:00:00 2001 From: Zoe Wang <33073555+zoewangg@users.noreply.github.com> Date: Wed, 29 Jul 2026 12:53:06 -0700 Subject: [PATCH 2/5] build: cap @SdkAdvancedApi guidance and saferAlternative length Add a source-AST checkstyle check that fails the build when a guidance or saferAlternative value on @SdkAdvancedApi exceeds 1000 characters. Values are multi-line string concatenations, so the check sums the decoded length of every concatenated literal. Keeps the text short enough to be useful inline; the longest current message is 637. --- build-tools/pom.xml | 12 ++ .../SdkAdvancedApiMemberLengthCheck.java | 146 ++++++++++++++++ .../software/amazon/awssdk/checkstyle.xml | 5 + .../SdkAdvancedApiMemberLengthCheckTest.java | 165 ++++++++++++++++++ 4 files changed, 328 insertions(+) create mode 100644 build-tools/src/main/java/software/amazon/awssdk/buildtools/checkstyle/SdkAdvancedApiMemberLengthCheck.java create mode 100644 build-tools/src/test/java/software/amazon/awssdk/buildtools/checkstyle/SdkAdvancedApiMemberLengthCheckTest.java diff --git a/build-tools/pom.xml b/build-tools/pom.xml index b48807ad8caf..9d55cdf9379c 100644 --- a/build-tools/pom.xml +++ b/build-tools/pom.xml @@ -59,6 +59,12 @@ true + + + org.apache.maven.plugins + maven-surefire-plugin + 3.1.2 + @@ -74,6 +80,12 @@ spotbugs 4.8.6 + + org.junit.jupiter + junit-jupiter + 5.10.3 + test + diff --git a/build-tools/src/main/java/software/amazon/awssdk/buildtools/checkstyle/SdkAdvancedApiMemberLengthCheck.java b/build-tools/src/main/java/software/amazon/awssdk/buildtools/checkstyle/SdkAdvancedApiMemberLengthCheck.java new file mode 100644 index 000000000000..3db26f317689 --- /dev/null +++ b/build-tools/src/main/java/software/amazon/awssdk/buildtools/checkstyle/SdkAdvancedApiMemberLengthCheck.java @@ -0,0 +1,146 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.buildtools.checkstyle; + +import com.puppycrawl.tools.checkstyle.api.AbstractCheck; +import com.puppycrawl.tools.checkstyle.api.DetailAST; +import com.puppycrawl.tools.checkstyle.api.FullIdent; +import com.puppycrawl.tools.checkstyle.api.TokenTypes; +import java.util.Arrays; +import java.util.List; + +/** + * Caps the length of the {@code guidance} and {@code saferAlternative} member values on any + * {@code @SdkAdvancedApi} usage. The values are written as multi-line string-literal concatenations, so this operates + * on the source AST and sums the decoded content of every concatenated string literal in the member's expression before + * comparing to {@code max}. + */ +public class SdkAdvancedApiMemberLengthCheck extends AbstractCheck { + + private static final String ANNOTATION_NAME = "SdkAdvancedApi"; + private static final List CHECKED_MEMBERS = Arrays.asList("guidance", "saferAlternative"); + + private int max = 1000; + + public void setMax(int max) { + this.max = max; + } + + @Override + public int[] getDefaultTokens() { + return getRequiredTokens(); + } + + @Override + public int[] getAcceptableTokens() { + return getRequiredTokens(); + } + + @Override + public int[] getRequiredTokens() { + return new int[] {TokenTypes.ANNOTATION}; + } + + @Override + public void visitToken(DetailAST annotation) { + if (!isSdkAdvancedApi(annotation)) { + return; + } + + for (DetailAST child = annotation.getFirstChild(); child != null; child = child.getNextSibling()) { + if (child.getType() != TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR) { + continue; + } + + DetailAST memberName = child.findFirstToken(TokenTypes.IDENT); + if (memberName == null || !CHECKED_MEMBERS.contains(memberName.getText())) { + continue; + } + + DetailAST expr = child.findFirstToken(TokenTypes.EXPR); + if (expr == null) { + continue; + } + + int length = concatenatedStringLength(expr); + if (length > max) { + log(memberName, String.format( + "@%s member '%s' is %d characters, which exceeds the maximum of %d. Tighten the wording.", + ANNOTATION_NAME, memberName.getText(), length, max)); + } + } + } + + private boolean isSdkAdvancedApi(DetailAST annotation) { + DetailAST nameNode = annotation.findFirstToken(TokenTypes.IDENT); + if (nameNode == null) { + nameNode = annotation.findFirstToken(TokenTypes.DOT); + } + if (nameNode == null) { + return false; + } + String name = FullIdent.createFullIdent(nameNode).getText(); + return name.equals(ANNOTATION_NAME) || name.endsWith("." + ANNOTATION_NAME); + } + + private int concatenatedStringLength(DetailAST node) { + int total = 0; + for (DetailAST child = node.getFirstChild(); child != null; child = child.getNextSibling()) { + if (child.getType() == TokenTypes.STRING_LITERAL) { + total += decodedLength(child.getText()); + } else { + total += concatenatedStringLength(child); + } + } + return total; + } + + /** + * Counts the logical characters in a string literal token (its text includes the surrounding quotes), treating each + * escape sequence as a single character so the count matches the text a consumer of the annotation would see. + */ + private int decodedLength(String literalWithQuotes) { + String body = literalWithQuotes; + if (body.length() >= 2 && body.charAt(0) == '"' && body.charAt(body.length() - 1) == '"') { + body = body.substring(1, body.length() - 1); + } + + int count = 0; + int i = 0; + while (i < body.length()) { + if (body.charAt(i) == '\\' && i + 1 < body.length()) { + char next = body.charAt(i + 1); + if (next == 'u') { + i += 6; + } else if (next >= '0' && next <= '7') { + int j = i + 1; + int digits = 0; + while (j < body.length() && digits < 3 && body.charAt(j) >= '0' && body.charAt(j) <= '7') { + j++; + digits++; + } + i = j; + } else { + i += 2; + } + } else { + i++; + } + count++; + } + return count; + } +} diff --git a/build-tools/src/main/resources/software/amazon/awssdk/checkstyle.xml b/build-tools/src/main/resources/software/amazon/awssdk/checkstyle.xml index 09c2082a8d40..857873374c96 100644 --- a/build-tools/src/main/resources/software/amazon/awssdk/checkstyle.xml +++ b/build-tools/src/main/resources/software/amazon/awssdk/checkstyle.xml @@ -444,6 +444,11 @@ + + + + + diff --git a/build-tools/src/test/java/software/amazon/awssdk/buildtools/checkstyle/SdkAdvancedApiMemberLengthCheckTest.java b/build-tools/src/test/java/software/amazon/awssdk/buildtools/checkstyle/SdkAdvancedApiMemberLengthCheckTest.java new file mode 100644 index 000000000000..003fec4458dc --- /dev/null +++ b/build-tools/src/test/java/software/amazon/awssdk/buildtools/checkstyle/SdkAdvancedApiMemberLengthCheckTest.java @@ -0,0 +1,165 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.buildtools.checkstyle; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.puppycrawl.tools.checkstyle.Checker; +import com.puppycrawl.tools.checkstyle.DefaultConfiguration; +import com.puppycrawl.tools.checkstyle.api.AuditEvent; +import com.puppycrawl.tools.checkstyle.api.AuditListener; +import com.puppycrawl.tools.checkstyle.api.CheckstyleException; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class SdkAdvancedApiMemberLengthCheckTest { + + @TempDir + Path tempDir; + + @Test + void guidanceWithinLimit_passes() throws Exception { + String source = classWithMembers(repeat("a", 50), repeat("b", 50)); + assertEquals(Collections.emptyList(), runCheck(source, 1000)); + } + + @Test + void guidanceOverLimit_fails() throws Exception { + String source = classWithMembers(repeat("a", 1001), repeat("b", 50)); + List violations = runCheck(source, 1000); + assertEquals(1, violations.size()); + assertTrue(violations.get(0).contains("guidance"), violations.get(0)); + assertTrue(violations.get(0).contains("1001"), violations.get(0)); + } + + @Test + void saferAlternativeOverLimit_fails() throws Exception { + String source = classWithMembers(repeat("a", 50), repeat("b", 1001)); + List violations = runCheck(source, 1000); + assertEquals(1, violations.size()); + assertTrue(violations.get(0).contains("saferAlternative"), violations.get(0)); + } + + @Test + void multiLineConcatenation_sumsAllFragments() throws Exception { + String guidance = "\"" + repeat("a", 400) + "\"\n" + + " + \"" + repeat("b", 400) + "\"\n" + + " + \"" + repeat("c", 300) + "\""; + String source = classWithRawGuidance(guidance); + List violations = runCheck(source, 1000); + assertEquals(1, violations.size()); + assertTrue(violations.get(0).contains("1100"), violations.get(0)); + } + + @Test + void valueAtExactLimit_passes() throws Exception { + String source = classWithMembers(repeat("a", 1000), repeat("b", 1000)); + assertEquals(Collections.emptyList(), runCheck(source, 1000)); + } + + private static String repeat(String s, int n) { + return IntStream.range(0, n).mapToObj(i -> s).collect(Collectors.joining()); + } + + private static String classWithMembers(String guidance, String saferAlternative) { + return classWithRawGuidanceAndSafer("\"" + guidance + "\"", "\"" + saferAlternative + "\""); + } + + private static String classWithRawGuidance(String rawGuidanceExpr) { + return classWithRawGuidanceAndSafer(rawGuidanceExpr, "\"safe\""); + } + + private static String classWithRawGuidanceAndSafer(String rawGuidanceExpr, String rawSaferExpr) { + return "package p;\n" + + "@SdkAdvancedApi(\n" + + " caution = Caution.WHEN_IMPLEMENTED,\n" + + " guidance = " + rawGuidanceExpr + ",\n" + + " saferAlternative = " + rawSaferExpr + ")\n" + + "public interface Foo {\n" + + "}\n"; + } + + private List runCheck(String source, int max) throws IOException, CheckstyleException { + File file = tempDir.resolve("Foo.java").toFile(); + Files.write(file.toPath(), source.getBytes(StandardCharsets.UTF_8)); + + DefaultConfiguration checkConfig = new DefaultConfiguration(SdkAdvancedApiMemberLengthCheck.class.getName()); + checkConfig.addAttribute("max", Integer.toString(max)); + + DefaultConfiguration treeWalker = new DefaultConfiguration("TreeWalker"); + treeWalker.addChild(checkConfig); + + DefaultConfiguration checker = new DefaultConfiguration("Checker"); + checker.addAttribute("charset", "UTF-8"); + checker.addChild(treeWalker); + + Checker c = new Checker(); + c.setModuleClassLoader(Thread.currentThread().getContextClassLoader()); + c.configure(checker); + + List messages = new ArrayList<>(); + c.addListener(new CollectingListener(messages)); + + c.process(Collections.singletonList(file)); + c.destroy(); + return messages; + } + + private static final class CollectingListener implements AuditListener { + private final List messages; + + private CollectingListener(List messages) { + this.messages = messages; + } + + @Override + public void addError(AuditEvent event) { + messages.add(event.getMessage()); + } + + @Override + public void addException(AuditEvent event, Throwable throwable) { + messages.add("EXCEPTION: " + throwable.getMessage()); + } + + @Override + public void auditStarted(AuditEvent event) { + } + + @Override + public void auditFinished(AuditEvent event) { + } + + @Override + public void fileStarted(AuditEvent event) { + } + + @Override + public void fileFinished(AuditEvent event) { + } + } +} From dfa5f99b38387ffd3b7dedcbeab386777d6ab2a5 Mon Sep 17 00:00:00 2001 From: Zoe Wang <33073555+zoewangg@users.noreply.github.com> Date: Wed, 29 Jul 2026 15:06:18 -0700 Subject: [PATCH 3/5] Address PR review feedback on @SdkAdvancedApi guidance Correct the ResponseTransformer guidance: the SDK (via CombinedResponseHandler) drains and closes the response stream after transform() returns, so the implementation does not need to close it; the prior 'must close' wording contradicted the interface Javadoc. Reword the FUTURE_COMPLETION_EXECUTOR guidance to describe the risky executor as one that does not dispatch to a separate thread rather than naming an I/O thread. Minor formatting reflow in the AsyncResponseTransformer guidance string. --- .../awssdk/core/async/AsyncResponseTransformer.java | 3 +-- .../core/client/config/SdkAdvancedAsyncClientOption.java | 8 ++++---- .../amazon/awssdk/core/sync/ResponseTransformer.java | 7 ++++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/async/AsyncResponseTransformer.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/async/AsyncResponseTransformer.java index 14a82b4760a7..c9491c17f942 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/async/AsyncResponseTransformer.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/async/AsyncResponseTransformer.java @@ -95,8 +95,7 @@ + "exceptionOccurred() should release any resources the attempt opened (for example an open file channel) " + "so retries do not leak them. onStream() receives the response body as a reactive-streams Publisher that " + "the implementation subscribes to, and that subscriber must comply with the reactive-streams " - + "specification; a subscriber that never " - + "requests data stalls the response.", + + "specification; a subscriber that never requests data stalls the response.", saferAlternative = "Prefer the AsyncResponseTransformer.toFile/toBytes/toBlockingInputStream factories.") public interface AsyncResponseTransformer { /** diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/config/SdkAdvancedAsyncClientOption.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/config/SdkAdvancedAsyncClientOption.java index 4c15c3c28804..1360b6cf614b 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/config/SdkAdvancedAsyncClientOption.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/config/SdkAdvancedAsyncClientOption.java @@ -56,10 +56,10 @@ public final class SdkAdvancedAsyncClientOption extends ClientOption { */ @SdkAdvancedApi( caution = Caution.WHEN_CONFIGURED, - guidance = "An executor that runs on the client's I/O threads (e.g. Runnable::run) can block " - + "them and deadlock the client, especially when the future chain issues another " - + "SDK request. If you set a custom executor, verify it under your own load tests, " - + "including request chains that issue further SDK calls.", + guidance = "An executor that does not dispatch to a separate thread (e.g. Runnable::run) runs the " + + "completion inline on the calling thread and can deadlock the client, especially when the " + + "future chain issues another SDK request. If you set a custom executor, verify it under your " + + "own load tests, including request chains that issue further SDK calls.", saferAlternative = "Leave unset to use the SDK-managed per-client ThreadPoolExecutor.") public static final SdkAdvancedAsyncClientOption FUTURE_COMPLETION_EXECUTOR = new SdkAdvancedAsyncClientOption<>(Executor.class); diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/sync/ResponseTransformer.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/sync/ResponseTransformer.java index 0ab24f5c119a..14380b708047 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/sync/ResponseTransformer.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/sync/ResponseTransformer.java @@ -79,9 +79,10 @@ @SdkPublicApi @SdkAdvancedApi( caution = Caution.WHEN_IMPLEMENTED, - guidance = "transform() must fully drain and close the response stream and honor thread interrupts. A " - + "transformer that blocks or returns without consuming the stream hangs the calling thread and leaks " - + "the underlying HTTP connection.", + guidance = "transform() runs on the calling thread; for long-running work, check the thread interrupt status " + + "and throw InterruptedException so the SDK can stop the request in a timely manner on timeout or " + + "cancellation. A transformer that blocks indefinitely stalls that thread. The SDK drains and closes " + + "the response stream after transform() returns, so the implementation does not need to close it.", saferAlternative = "Prefer the ResponseTransformer.toFile/toBytes/toOutputStream/toInputStream factories.") public interface ResponseTransformer { /** From aaa1cf63703badd1dba99d364f7a47ae681c3aea Mon Sep 17 00:00:00 2001 From: Zoe Wang <33073555+zoewangg@users.noreply.github.com> Date: Wed, 29 Jul 2026 16:06:30 -0700 Subject: [PATCH 4/5] Remove @SdkAdvancedApi from ResponseTransformer Scope the initial rollout to the higher-severity extension points. The sync ResponseTransformer is a plain callback whose only contract is thread-interrupt handling (low priority); revisit if warranted. --- .../next-release/feature-AWSSDKforJavav2-6933447.json | 2 +- .../amazon/awssdk/core/sync/ResponseTransformer.java | 9 --------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/.changes/next-release/feature-AWSSDKforJavav2-6933447.json b/.changes/next-release/feature-AWSSDKforJavav2-6933447.json index adab20bdb298..05f531778c87 100644 --- a/.changes/next-release/feature-AWSSDKforJavav2-6933447.json +++ b/.changes/next-release/feature-AWSSDKforJavav2-6933447.json @@ -2,5 +2,5 @@ "type": "feature", "category": "AWS SDK for Java v2", "contributor": "", - "description": "Add the `@SdkAdvancedApi` annotation, which marks APIs that are error-prone to implement, override, call, or configure so that using them incorrectly compiles cleanly but can fail or misbehave at runtime. The annotation records structured guidance (the risky usage kind, an explanation of the contract to uphold, a safer alternative, and a documentation link) and is applied to several streaming and interceptor extension points, including AsyncRequestBody, AsyncResponseTransformer, ResponseTransformer, ContentStreamProvider, the mutating ExecutionInterceptor content hooks, and the FUTURE_COMPLETION_EXECUTOR advanced client option." + "description": "Add the `@SdkAdvancedApi` annotation, which marks APIs that are error-prone to implement, override, call, or configure so that using them incorrectly compiles cleanly but can fail or misbehave at runtime. The annotation records structured guidance (the risky usage kind, an explanation of the contract to uphold, a safer alternative, and a documentation link) and is applied to several streaming and interceptor extension points, including AsyncRequestBody, AsyncResponseTransformer, ContentStreamProvider, the mutating ExecutionInterceptor content hooks, and the FUTURE_COMPLETION_EXECUTOR advanced client option." } diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/sync/ResponseTransformer.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/sync/ResponseTransformer.java index 14380b708047..279c29c29c3c 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/sync/ResponseTransformer.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/sync/ResponseTransformer.java @@ -28,8 +28,6 @@ import java.nio.file.Path; import java.time.Duration; import java.util.Map; -import software.amazon.awssdk.annotations.SdkAdvancedApi; -import software.amazon.awssdk.annotations.SdkAdvancedApi.Caution; import software.amazon.awssdk.annotations.SdkProtectedApi; import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.core.ResponseBytes; @@ -77,13 +75,6 @@ */ @FunctionalInterface @SdkPublicApi -@SdkAdvancedApi( - caution = Caution.WHEN_IMPLEMENTED, - guidance = "transform() runs on the calling thread; for long-running work, check the thread interrupt status " - + "and throw InterruptedException so the SDK can stop the request in a timely manner on timeout or " - + "cancellation. A transformer that blocks indefinitely stalls that thread. The SDK drains and closes " - + "the response stream after transform() returns, so the implementation does not need to close it.", - saferAlternative = "Prefer the ResponseTransformer.toFile/toBytes/toOutputStream/toInputStream factories.") public interface ResponseTransformer { /** * Process the response contents. From b1f20bfc464c49ab103c083e9800421c023ca914 Mon Sep 17 00:00:00 2001 From: Zoe Wang <33073555+zoewangg@users.noreply.github.com> Date: Thu, 30 Jul 2026 12:47:41 -0700 Subject: [PATCH 5/5] Rename @SdkAdvancedApi caution() to cautionWhen() and Caution to Usage Per API review feedback: cautionWhen = Usage.IMPLEMENTED reads more naturally than caution = Caution.WHEN_IMPLEMENTED. Values drop the WHEN_ prefix since the member now carries it. --- .../SdkAdvancedApiMemberLengthCheckTest.java | 2 +- .../awssdk/annotations/SdkAdvancedApi.java | 20 +++++++++---------- .../awssdk/core/async/AsyncRequestBody.java | 6 +++--- .../core/async/AsyncResponseTransformer.java | 8 ++++---- .../config/SdkAdvancedAsyncClientOption.java | 4 ++-- .../interceptor/ExecutionInterceptor.java | 6 +++--- .../awssdk/http/ContentStreamProvider.java | 4 ++-- 7 files changed, 25 insertions(+), 25 deletions(-) diff --git a/build-tools/src/test/java/software/amazon/awssdk/buildtools/checkstyle/SdkAdvancedApiMemberLengthCheckTest.java b/build-tools/src/test/java/software/amazon/awssdk/buildtools/checkstyle/SdkAdvancedApiMemberLengthCheckTest.java index 003fec4458dc..450959528325 100644 --- a/build-tools/src/test/java/software/amazon/awssdk/buildtools/checkstyle/SdkAdvancedApiMemberLengthCheckTest.java +++ b/build-tools/src/test/java/software/amazon/awssdk/buildtools/checkstyle/SdkAdvancedApiMemberLengthCheckTest.java @@ -96,7 +96,7 @@ private static String classWithRawGuidance(String rawGuidanceExpr) { private static String classWithRawGuidanceAndSafer(String rawGuidanceExpr, String rawSaferExpr) { return "package p;\n" + "@SdkAdvancedApi(\n" - + " caution = Caution.WHEN_IMPLEMENTED,\n" + + " cautionWhen = Usage.IMPLEMENTED,\n" + " guidance = " + rawGuidanceExpr + ",\n" + " saferAlternative = " + rawSaferExpr + ")\n" + "public interface Foo {\n" diff --git a/core/annotations/src/main/java/software/amazon/awssdk/annotations/SdkAdvancedApi.java b/core/annotations/src/main/java/software/amazon/awssdk/annotations/SdkAdvancedApi.java index 2da9154ecaa1..e7d5dab98bcb 100644 --- a/core/annotations/src/main/java/software/amazon/awssdk/annotations/SdkAdvancedApi.java +++ b/core/annotations/src/main/java/software/amazon/awssdk/annotations/SdkAdvancedApi.java @@ -26,7 +26,7 @@ * configuring it with an unsafe value, compiles cleanly but can fail or misbehave at runtime rather * than reporting a clear error. * - *

The annotation records the risk information on the API in a structured form: {@link #caution()} + *

The annotation records the risk information on the API in a structured form: {@link #cautionWhen()} * classifies which kind of use is error-prone, {@link #guidance()} explains the contract that must * be upheld, {@link #saferAlternative()} points to a lower-risk approach, and {@link #link()} points * to further documentation. @@ -40,13 +40,13 @@ public @interface SdkAdvancedApi { /** - * Which kind of use of this API is error-prone: WHEN_IMPLEMENTED if the risk is in implementing - * or extending the annotated type, WHEN_OVERRIDDEN if it is in overriding the annotated method, - * WHEN_CONFIGURED if it is in setting the annotated field or option, and WHEN_CALLED if it is in + * Which kind of use of this API is error-prone: IMPLEMENTED if the risk is in implementing + * or extending the annotated type, OVERRIDDEN if it is in overriding the annotated method, + * CONFIGURED if it is in setting the annotated field or option, and CALLED if it is in * calling the annotated method (for example a factory that accepts an object you supply and does * not shield you from that object's contract). Required. */ - Caution caution(); + Usage cautionWhen(); /** * Explains why this API is error-prone and what you must uphold to use it safely: the parts of @@ -67,10 +67,10 @@ */ String link() default ""; - enum Caution { - WHEN_IMPLEMENTED, - WHEN_OVERRIDDEN, - WHEN_CONFIGURED, - WHEN_CALLED + enum Usage { + IMPLEMENTED, + OVERRIDDEN, + CONFIGURED, + CALLED } } diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/async/AsyncRequestBody.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/async/AsyncRequestBody.java index fba1e50b0838..d762dc15a809 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/async/AsyncRequestBody.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/async/AsyncRequestBody.java @@ -30,7 +30,7 @@ import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; import software.amazon.awssdk.annotations.SdkAdvancedApi; -import software.amazon.awssdk.annotations.SdkAdvancedApi.Caution; +import software.amazon.awssdk.annotations.SdkAdvancedApi.Usage; import software.amazon.awssdk.annotations.SdkProtectedApi; import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.core.FileRequestBodyConfiguration; @@ -66,7 +66,7 @@ */ @SdkPublicApi @SdkAdvancedApi( - caution = Caution.WHEN_IMPLEMENTED, + cautionWhen = Usage.IMPLEMENTED, guidance = "This is a reactive-streams Publisher and must obey the reactive-streams specification. " + "The SDK re-subscribes on each retry attempt, " + "so to support retries, reproduce the full content on every subscribe; a body that cannot should fail the " @@ -106,7 +106,7 @@ default String body() { * @return Implementation of {@link AsyncRequestBody} that produces data send by the publisher */ @SdkAdvancedApi( - caution = Caution.WHEN_CALLED, + cautionWhen = Usage.CALLED, guidance = "The returned AsyncRequestBody passes each subscribe straight through to the supplied publisher, so " + "the publisher must itself meet the AsyncRequestBody contract: obey the reactive-streams " + "specification, and since the SDK re-subscribes on each " diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/async/AsyncResponseTransformer.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/async/AsyncResponseTransformer.java index c9491c17f942..70ff1e6aef50 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/async/AsyncResponseTransformer.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/async/AsyncResponseTransformer.java @@ -24,7 +24,7 @@ import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; import software.amazon.awssdk.annotations.SdkAdvancedApi; -import software.amazon.awssdk.annotations.SdkAdvancedApi.Caution; +import software.amazon.awssdk.annotations.SdkAdvancedApi.Usage; import software.amazon.awssdk.annotations.SdkProtectedApi; import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.core.FileTransformerConfiguration; @@ -89,7 +89,7 @@ */ @SdkPublicApi @SdkAdvancedApi( - caution = Caution.WHEN_IMPLEMENTED, + cautionWhen = Usage.IMPLEMENTED, guidance = "prepare() is called on each request attempt; if the CompletableFuture it returned on a previous " + "attempt has already completed, it must return a new instance so the result of a retry is not lost, and " + "exceptionOccurred() should release any resources the attempt opened (for example an open file channel) " @@ -316,7 +316,7 @@ static AsyncResponseTransformer> * @return AsyncResponseTransformer instance. */ @SdkAdvancedApi( - caution = Caution.WHEN_CALLED, + cautionWhen = Usage.CALLED, guidance = "The returned ResponsePublisher is a reactive-streams Publisher you must subscribe to and drive: " + "your subscriber must obey the reactive-streams specification and honor back-pressure. A subscriber " + "that never requests data stalls the response, and requesting unbounded data can exhaust memory.", @@ -351,7 +351,7 @@ static AsyncResponseTransformer extends ClientOption { * another SDK request in the {@link CompletableFuture} chain since the SDK may perform blocking calls in some cases. */ @SdkAdvancedApi( - caution = Caution.WHEN_CONFIGURED, + cautionWhen = Usage.CONFIGURED, guidance = "An executor that does not dispatch to a separate thread (e.g. Runnable::run) runs the " + "completion inline on the calling thread and can deadlock the client, especially when the " + "future chain issues another SDK request. If you set a custom executor, verify it under your " diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/interceptor/ExecutionInterceptor.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/interceptor/ExecutionInterceptor.java index 0789d8d2053e..73326f170095 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/interceptor/ExecutionInterceptor.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/interceptor/ExecutionInterceptor.java @@ -20,7 +20,7 @@ import java.util.Optional; import org.reactivestreams.Publisher; import software.amazon.awssdk.annotations.SdkAdvancedApi; -import software.amazon.awssdk.annotations.SdkAdvancedApi.Caution; +import software.amazon.awssdk.annotations.SdkAdvancedApi.Usage; import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.core.SdkRequest; import software.amazon.awssdk.core.SdkResponse; @@ -186,7 +186,7 @@ default SdkHttpRequest modifyHttpRequest(Context.ModifyHttpRequest context, } @SdkAdvancedApi( - caution = Caution.WHEN_OVERRIDDEN, + cautionWhen = Usage.OVERRIDDEN, guidance = "Replacing the request body from an interceptor is discouraged: the returned RequestBody becomes the " + "content the SDK sends, and the request's Content-Length and content hash or checksum are derived from " + "it, so a body whose content or length does not match what the operation expects corrupts the request " @@ -200,7 +200,7 @@ default Optional modifyHttpContent(Context.ModifyHttpRequest contex } @SdkAdvancedApi( - caution = Caution.WHEN_OVERRIDDEN, + cautionWhen = Usage.OVERRIDDEN, guidance = "Replacing the async request body from an interceptor is discouraged: the returned AsyncRequestBody " + "becomes the content the SDK sends, and the request's Content-Length and content hash or checksum are " + "derived from it, so a body whose content or length does not match what the operation expects corrupts " diff --git a/http-client-spi/src/main/java/software/amazon/awssdk/http/ContentStreamProvider.java b/http-client-spi/src/main/java/software/amazon/awssdk/http/ContentStreamProvider.java index a129c42bd3f6..714a960a9262 100644 --- a/http-client-spi/src/main/java/software/amazon/awssdk/http/ContentStreamProvider.java +++ b/http-client-spi/src/main/java/software/amazon/awssdk/http/ContentStreamProvider.java @@ -25,7 +25,7 @@ import java.util.Map; import java.util.function.Supplier; import software.amazon.awssdk.annotations.SdkAdvancedApi; -import software.amazon.awssdk.annotations.SdkAdvancedApi.Caution; +import software.amazon.awssdk.annotations.SdkAdvancedApi.Usage; import software.amazon.awssdk.annotations.SdkProtectedApi; import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.utils.IoUtils; @@ -44,7 +44,7 @@ @SdkPublicApi @FunctionalInterface @SdkAdvancedApi( - caution = Caution.WHEN_IMPLEMENTED, + cautionWhen = Usage.IMPLEMENTED, guidance = "newStream() must return a stream positioned at the start of the content on every call, closing any " + "previous stream it opened. A stale or unreset stream corrupts the request body and breaks retries, " + "which re-read the content.",