Skip to content

KmsClient.encrypt leaked CompletionException for checked IdentityProvider failures before 2.47.0 #7369

Closed

Description

@stevenwarejones

Update (2026-09-11): This was a real KmsClient.encrypt behavior in older SDK versions, but it is already fixed in current releases.

AWS SDK version Exception from KmsClient.encrypt Cause
2.23.18 CompletionException GeneralSecurityException
2.46.21 CompletionException GeneralSecurityException
2.47.0 SdkClientException GeneralSecurityException
2.54.14 SdkClientException GeneralSecurityException

The 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 ExecutionFailureExceptionReportingStage and ThrowableUtils.failure, which converts the checked cause to SdkClientException. 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

  1. changed the title [-]Synchronous clients leak CompletionException for checked IdentityProvider failures[/-] [+]Synchronous clients leaked CompletionException for checked IdentityProvider failures before 2.47.0[/+] on Sep 11, 2026
  2. github-actions commented on Sep 11, 2026

    @github-actions

    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.

  3. changed the title [-]Synchronous clients leaked CompletionException for checked IdentityProvider failures before 2.47.0[/-] [+]KmsClient.encrypt leaked CompletionException for checked IdentityProvider failures before 2.47.0[/+] on Sep 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions