Repository navigation
Description
Update (2026-09-11): This was a real
KmsClient.encryptbehavior in older SDK versions, but it is already fixed in current releases.
AWS SDK version Exception from KmsClient.encryptCause 2.23.18 CompletionExceptionGeneralSecurityException2.46.21 CompletionExceptionGeneralSecurityException2.47.0 SdkClientExceptionGeneralSecurityException2.54.14 SdkClientExceptionGeneralSecurityExceptionThe version boundary coincides with #7017. That change moved identity resolution into the shared request pipeline; the 2.47.0 stack now passes the failure through
ExecutionFailureExceptionReportingStageandThrowableUtils.failure, which converts the checked cause toSdkClientException. Closing because current releases behave as expected.
Describe the bug
Before 2.47.0, KmsClient.encrypt could expose java.util.concurrent.CompletionException when its asynchronous IdentityProvider completed exceptionally with a checked exception.
The old synchronous authentication path waited through CompletableFutureUtils.joinLikeSync. That helper rethrew runtime causes directly but left checked causes wrapped in CompletionException. The async-specific wrapper therefore escaped from the public synchronous KMS call.
The IdentityProvider.resolveIdentity documentation does not state that its future must fail only with runtime exceptions. The runtime-exception statement on AwsCredentialsProvider documents resolveCredentials, not resolveIdentity.
Expected behavior
The synchronous KMS client should translate a checked identity-resolution failure to an SDK runtime exception such as SdkClientException, preserving the original cause. This is the behavior starting in 2.47.0.
Reproduction
This calls the public synchronous KmsClient.encrypt API; it does not call joinLikeSync directly. Credential resolution fails during request setup, before a KMS network request, and the provider is invoked once.
Run this class with software.amazon.awssdk:kms 2.46.21 and then 2.47.0:
import java.security.GeneralSecurityException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicInteger;
import software.amazon.awssdk.core.SdkBytes;
import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity;
import software.amazon.awssdk.identity.spi.IdentityProvider;
import software.amazon.awssdk.identity.spi.ResolveIdentityRequest;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.kms.KmsClient;
import software.amazon.awssdk.services.kms.model.EncryptRequest;
public final class Repro {
public static void main(String[] args) {
AtomicInteger resolveCalls = new AtomicInteger();
GeneralSecurityException failure =
new GeneralSecurityException("credential resolution failed");
IdentityProvider<AwsCredentialsIdentity> provider =
new IdentityProvider<>() {
@Override
public Class<AwsCredentialsIdentity> identityType() {
return AwsCredentialsIdentity.class;
}
@Override
public CompletableFuture<? extends AwsCredentialsIdentity> resolveIdentity(
ResolveIdentityRequest request) {
resolveCalls.incrementAndGet();
return CompletableFuture.failedFuture(failure);
}
};
try (KmsClient kms =
KmsClient.builder()
.region(Region.US_EAST_1)
.credentialsProvider(provider)
.build()) {
kms.encrypt(
EncryptRequest.builder()
.keyId("arn:aws:kms:us-east-1:123456789012:key/test")
.plaintext(SdkBytes.fromUtf8String("test"))
.build());
throw new AssertionError("Expected encrypt to fail");
} catch (RuntimeException e) {
System.out.println("THROWN=" + e.getClass().getName());
System.out.println("CAUSE=" + e.getCause().getClass().getName());
System.out.println("RESOLVE_CALLS=" + resolveCalls.get());
System.out.println("TOP_FRAME=" + e.getStackTrace()[0]);
}
}
}With 2.46.21:
THROWN=java.util.concurrent.CompletionException
CAUSE=java.security.GeneralSecurityException
RESOLVE_CALLS=1
TOP_FRAME=software.amazon.awssdk.utils.CompletableFutureUtils.joinInterruptibly(...)
With 2.47.0:
THROWN=software.amazon.awssdk.core.exception.SdkClientException
CAUSE=java.security.GeneralSecurityException
RESOLVE_CALLS=1
TOP_FRAME=software.amazon.awssdk.core.exception.SdkClientException$BuilderImpl.build(...)
Additional context
PR #3953 introduced joinLikeSync with the stated goal of making asynchronous identity-resolution failures appear synchronous. Its utility-level test intentionally preserves CompletionException for a checked cause, but that does not establish that the exception should escape from a public synchronous service-client method.
This affected Tink AWS KMS because its synchronous Aead implementation expected SDK failures but could receive the leaked CompletionException: tink-crypto/tink-java-awskms#5
Versions tested
- AWS SDK 2.23.18, 2.46.21, 2.47.0, and 2.54.14
- JDK 17
- Linux
Activity
[-]Synchronous clients leak CompletionException for checked IdentityProvider failures[/-][+]Synchronous clients leaked CompletionException for checked IdentityProvider failures before 2.47.0[/+]github-actions commentedon Sep 11, 2026
This issue is now closed. Comments on closed issues are hard for our team to see.
If you need more assistance, please open a new issue that references this one.
[-]Synchronous clients leaked CompletionException for checked IdentityProvider failures before 2.47.0[/-][+]KmsClient.encrypt leaked CompletionException for checked IdentityProvider failures before 2.47.0[/+]