Common features
Named clients
| This feature is new and was initially designed to allow overriding credentials per named client. Feel free to open an issue to propose enhancements. |
You can inject named clients with different configurations. To do this, annotate your injection point with @AmazonClient.
import io.quarkiverse.amazon.common.AmazonClient;
public class DynamoDbEnhancedClientTest {
@Inject
@AmazonClient("custom")
DynamoDbClient clientNamedCustom;
With a dedicated extension, named clients inherit the configuration of the unnamed client, and you can override it:
quarkus.dynamodb."custom".aws.credentials.type=static
quarkus.dynamodb."custom".aws.credentials.static-provider.access-key-id=xxx
quarkus.dynamodb."custom".aws.credentials.static-provider.secret-access-key=yyy
For clients managed by the generic AWS SDK extension, use quarkus.aws."client-name" instead:
quarkus.aws.region=eu-west-1
quarkus.aws.api-call-timeout=10s
quarkus.aws."custom".region=us-west-2
quarkus.aws."custom".credentials.type=static
quarkus.aws."custom".credentials.static-provider.access-key-id=xxx
quarkus.aws."custom".credentials.static-provider.secret-access-key=yyy
Here, the custom client uses us-west-2 and inherits the shared 10-second timeout from quarkus.aws.
A generic client name applies across services: clients injected with the same name use the same configuration.
Use different names when they need different settings.
Named generic clients also support different Dev Services providers
through quarkus.aws."client-name".devservices.provider, inheriting the global provider when unset.
Synthetic bean injection
Synthetic beans require manual registration of the client usage. This is because synthetic beans are only scanned
for dependencies after standard beans. Due to this, when the bean BeanRegistrationPhaseBuildItem is consumed,
and injection points are scanned, injection points the synthetic beans are not found.
To create a synthetic bean, use to following pattern. This example uses the async ECR client, however, the same principle applies to all clients. Refer to the generated test cases for an example of each client.
Create the processor class
If developing an extension, the processor class is placed into the deployment module. General practice
is to place it in the <package name>.deployment package.
import io.quarkiverse.amazon.common.deployment.RequireAmazonClientInjectionBuildItem;
import io.quarkiverse.amazon.common.runtime.ClientUtil;
import io.quarkiverse.amazon.ecr.EcrTestRecorder;
import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
import io.quarkus.arc.processor.DotNames;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.ExecutionTime;
import io.quarkus.deployment.annotations.Record;
import jakarta.inject.Singleton;
import org.jboss.jandex.ClassType;
import org.jboss.jandex.DotName;
import org.jboss.jandex.ParameterizedType;
import software.amazon.awssdk.services.ecr.EcrAsyncClient;
public class ExampleProcessor {
@Record(ExecutionTime.STATIC_INIT) (1)
@BuildStep
public void registerExampleBean(
EcrTestRecorder recorder,
BuildProducer<SyntheticBeanBuildItem> syntheticBeanProducer,
BuildProducer<RequireAmazonClientInjectionBuildItem> requireAmazonClientInjectionProducer) {
var clientClassName = DotName.createSimple(EcrAsyncClient.class); (2)
syntheticBeanProducer.produce(
SyntheticBeanBuildItem.configure(SyntheticBean.class) (3)
.scope(Singleton.class) (4)
.addInjectionPoint(ParameterizedType.create(
DotNames.INSTANCE,
ClassType.create(clientClassName)
))
.createWith(recorder.createBean()) (5)
.done()
);
requireAmazonClientInjectionProducer.produce( (6)
new RequireAmazonClientInjectionBuildItem(clientClassName, ClientUtil.DEFAULT_CLIENT_NAME) (7)
);
}
}
| 1 | Can also be RUNTIME_INIT if required. |
| 2 | The client class required |
| 3 | The Synthetic bean you being created |
| 4 | Or a different scope that is required (ApplicationScoped etc) |
| 5 | The recorder function. If using SyntheticBeanBuildItem#createWith(Function), the bytecode produced by the
function will be recorded, allowing for injection. This will not work with
SyntheticBeanBuildItem#runtimeValue(RuntimeValue). However, if using
SyntheticBeanBuildItem#runtimeValue(RuntimeValue), this approach can still be used to force the client inclusion
and then use dynamic bean lookups to obtain the actual client. |
| 6 | Force the inclusion of the client into the build. Without this line, the client bean will be unresolved. |
| 7 | If a different @Named annotation is required, set the name at this point. |
As a side note, if creating a processor in the test sources, do not rely on the processor being found
in the META-INF/quarkus-build-steps.list from the QuarkusExtensionTest. The Quarkus bootstrap does not
examine the test (generated) sources for this file. Do not adjust the classpath to include the file, it will
create a race-condition where one test loads the processor list for a different test. Always manually create
the META-INF/quarkus-build-steps.list manually using ShrinkWrap#addAsResource.
Create the recorder class
If developing an extension, the processor class is placed into the runtime module. General practice
is to place it in the <package name> package.
import io.quarkus.arc.SyntheticCreationalContext;
import io.quarkus.runtime.annotations.Recorder;
import jakarta.enterprise.inject.Instance;
import jakarta.enterprise.util.TypeLiteral;
import java.util.function.Function;
import software.amazon.awssdk.services.ecr.EcrAsyncClient;
@Recorder
public class EcrTestRecorder {
public EcrTestRecorder() {}
public Function<SyntheticCreationalContext<SyntheticBean>, SyntheticBean> createBean() {
return context -> { (1)
var ref = context.getInjectedReference(new TypeLiteral<Instance<EcrAsyncClient>>() {}); (2)
return new SyntheticBean(ref);
};
}
}
| 1 | The Function return. The result of this call, i.e. the Function lambda is recorded at build time
and replayed at runtime to create the bean. The lambda itself is not evaluated at build time. |
| 2 | Get a reference to the injected bean, which is then passed to the constructor. Do not use the actual
bean, or resolve it through Instance#get() at this point as the client bean may not have been created. |
Create the bean class
If developing an extension, the processor class is placed into the runtime module. General practice
is to place it in the <package name>.runtime package.
import jakarta.enterprise.inject.Instance;
import software.amazon.awssdk.services.ecr.EcrAsyncClient;
public class SyntheticBean {
private Instance<EcrAsyncClient> client;
public SyntheticBean(Instance<EcrAsyncClient> bean) { (1)
this.bean = bean;
}
public void invoke() {
this.bean.get(); (2)
}
}
| 1 | The bean instance is injected at this point. Since this is during startup, the actual bean may not be ready for use yet. |
| 2 | Invoke actions on the bean. |
The synthetic bean class can be injected into any other class using the standard @Inject SyntheticBean bean
approach. Be sure to only call methods invoking the bean after full startup of the CDI container.
Overriding the client configuration
You can override the client configuration by adding a custom producer that will further configure the client builder built by the extension.
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Produces;
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient;
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClientBuilder;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.DynamoDbClientBuilder;
@ApplicationScoped
public class DynamodbProducer {
private static final DynamoDBModifyResponse EXECUTION_INTERCEPTOR = new io.quarkiverse.it.amazon.dynamodb.DynamoDBModifyResponse();
@Produces
@ApplicationScoped
public DynamoDbClient createDynamoDbClient(DynamoDbClientBuilder builder) {
builder.overrideConfiguration(builder.overrideConfiguration().toBuilder()
.addExecutionInterceptor(EXECUTION_INTERCEPTOR)
.build());
return builder.build();
}
@Produces
@ApplicationScoped
public DynamoDbAsyncClient createDynamoDbClient(DynamoDbAsyncClientBuilder builder) {
builder.overrideConfiguration(builder.overrideConfiguration().toBuilder()
.addExecutionInterceptor(EXECUTION_INTERCEPTOR)
.build());
return builder.build();
}
}
overrideConfiguration(…) and asyncConfiguration(…) replace the whole configuration object set on the builder.
Extend the configuration of the extension with builder.overrideConfiguration().toBuilder(), as above, rather than replacing it.
Vert.x context propagation is kept either way, but replacing asyncConfiguration drops the
configured completion executor.
|
Vert.x context propagation
| This feature is experimental. Please share your feedback on its behavior and compatibility with your applications. |
Without context propagation, the AWS SDK normally completes async results on the Quarkus worker pool, or the Netty/CRT event loop
when quarkus.<service>.async-client.advanced.use-future-completion-thread-pool is false.
These threads do not carry the Vert.x context of the caller. Libraries that rely on it, such as Hibernate Reactive, then fail with
No current Vertx context found.
When the Quarkus Vert.x extension is present (e.g. with Quarkus REST or Hibernate Reactive), calls started on an event loop complete on that same context by default. There is nothing to capture or switch manually:
import io.quarkus.hibernate.reactive.panache.common.WithTransaction;
@Inject
DynamoDbAsyncClient dynamoDb;
@WithTransaction
public Uni<Fruit> importFruit(String name) {
return Uni.createFrom().completionStage(() -> dynamoDb.getItem(request(name)))
// runs on the caller's Vert.x context, so the Hibernate Reactive session is available
.map(item -> toFruit(item))
.call(fruit -> fruit.persist());
}
This example assumes that Fruit extends io.quarkus.hibernate.reactive.panache.PanacheEntity.
@WithTransaction provides the reactive transaction, and call waits for persistence while retaining the Fruit result.
See the Hibernate Reactive with Panache guide.
The behavior is controlled with quarkus.<service>.async-client.advanced.vertx-context-propagation.
For clients managed by the generic quarkus-amazon-sdk extension, use
quarkus.aws.async-client.advanced.vertx-context-propagation. This setting applies to all generic services and named clients.
Dedicated service extensions keep their own service-specific setting, even when both extension types are installed.
The setting is a boolean:
| Value | Behavior |
|---|---|
|
A call made from a Vert.x event loop is completed on the caller’s context.
Calls made from workers (including |
|
Futures are completed on the SDK threads, as in previous versions. |
vertx-context-propagation and use-future-completion-thread-pool control different aspects of completion:
-
use-future-completion-thread-pool=true(default) processes HTTP attempt completion on the Quarkus worker pool;falsedoes not offload that processing, which normally runs on the HTTP client’s I/O thread. -
When propagation applies, the final result is then delivered on the selected Vert.x event loop, regardless of the pool setting.
-
With
false, without Vert.x, or for a call that is not made from an event loop, there is no additional context switch. The result follows the SDK completion path. Timeouts can complete on the SDK scheduler. -
An executor supplied through
FUTURE_COMPLETION_EXECUTORreplaces the extension’s pool selection, but does not disable the configured Vert.x propagation of the final result. -
The dedicated S3 CRT client follows
quarkus.s3.async-client.advanced.use-future-completion-thread-pooltoo: when it isfalse, its futures are completed on the AWS CRT event loop threads, which must not be blocked.
Context propagation applies to both Netty and AWS CRT HTTP transports and to the dedicated S3 CRT client.
It also applies to the final futures of putObject, getObject and copyObject when Java multipart support is enabled with
multipartEnabled(true) on the provided standard S3 builder, including upload-source failures before an HTTP request is sent.
Only the result returned to the application is meant to be delivered on the Vert.x context. The requests the SDK makes on its own behalf while starting a multipart operation, or chains once a previous response completed, keep the SDK completion threads. Requests issued from an event loop by other means, e.g. the parts of an upload whose body is produced on an event loop, may still be dispatched to it. Applications without the Vert.x extension keep the existing executor behavior, as do synchronous clients.
To restore the previous behavior, set the property to false for each service:
quarkus.dynamodb.async-client.advanced.vertx-context-propagation=false
# All clients managed by the generic SDK extension
quarkus.aws.async-client.advanced.vertx-context-propagation=false
# Applies to both the standard S3 async client and the dedicated S3 CRT client
quarkus.s3.async-client.advanced.vertx-context-propagation=false
Context propagation does not apply to:
-
execution interceptor hooks, including
afterExecutionandonExecutionFailure, which run before final-result delivery, -
the callbacks of streaming operations (
AsyncRequestBody,AsyncResponseTransformer), whose execution context is not guaranteed, -
the requests the SDK sends from its own threads, so it is not guaranteed for the
Publisherreturned by paginators, the futures returned bywaiter()and the futures returned by the S3 Transfer Manager, -
the futures returned by the
presignedUrlExtension()of an S3 client with multipart support, whose parts may be processed on an event loop, -
async clients built without the builder provided by the extension.
Context propagation relies on transforming a few internal AWS SDK classes at build time. If the AWS SDK version used by the application changed them, the build logs a warning and the SDK completion behavior is kept.
When completing on an event loop, application continuations and metric publishers must not block.
Context propagation is not guaranteed if the target Vert.x context can no longer accept tasks, for example during shutdown.
A continuation registered after a
CompletableFuture has completed may run on the registering thread; *Async continuations use their own executor.
A future shared by several requests, e.g. a cached value loaded asynchronously, completes on the context of the request that started the call: the continuations other requests chain to it then run on that context. Chain from a call made by the current request instead, or switch back to its own context.
Never block an event loop thread waiting for an async client future (e.g. with join()): its completion is dispatched
to that same event loop, which leads to a deadlock.
|
Custom AWS credentials provider
When configuring credentials type for a given client, you may not find the supported methods fit your context. In such case, you can implement
and provide your own AwsCredentialsProvider as a bean.
As an example, we will implement a provider for a S3 client application hosted in EKS.
The provider named eks-iam can be implemented with a @Produces method:
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Produces;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
import software.amazon.awssdk.auth.credentials.WebIdentityTokenFileCredentialsProvider;
@Produces
@ApplicationScoped
@Named("eks-iam")
public AwsCredentialsProvider getAwsCredentialProvider() {
String tokenFile = System.getenv("AWS_WEB_IDENTITY_TOKEN_FILE");
String roleArn = System.getenv("AWS_ROLE_ARN");
return (tokenFile != null && roleArn != null)
? WebIdentityTokenFileCredentialsProvider.builder()
.webIdentityTokenFile(Paths.get(tokenFile))
.roleArn(roleArn)
.build()
: DefaultCredentialsProvider.create();
}
Configure the default S3 client to use the custom provider for authentication:
quarkus.s3.aws.credentials.type=custom
quarkus.s3.aws.credentials.custom-provider.name=eks-iam
As this provider use WebIdentityTokenFileCredentialsProvider, add the following dependency to the application pom.xml:
<dependency>
<groupId>io.quarkiverse.amazonservices</groupId>
<artifactId>quarkus-amazon-sts</artifactId>
</dependency>
Deprecated Apache HTTP client
The AWS SDK has deprecated the Apache HTTP client (apache-client) in favor of the Apache 5.x HTTP client (apache5-client), which is a drop-in replacement.
To migrate a client, set quarkus.<client>.sync-client.type=apache5 and replace the software.amazon.awssdk:apache-client dependency with software.amazon.awssdk:apache5-client.
The quarkus.<client>.sync-client.apache.* properties apply to both clients.
The build logs a warning when the Apache HTTP client is configured for a client, or when no sync client is configured and apache-client is on the classpath.
To disable this warning, set:
quarkus.aws.sync-client.apache-deprecation-warning=false
Configuration property fixed at build time - All other configuration properties are overridable at runtime
Configuration property |
Type |
Default |
|---|---|---|
Whether to log a warning at build time when the Apache HTTP client is used. The AWS SDK deprecated the Apache HTTP client ( Environment variable: |
boolean |
|
Docker image build with AWS CRT
Since AWS CRT version 0.31.0, native library must be embedded in the docker image. This is not done by default. To do this, you need to :
-
edit .dockerignore to add:
!target/*.so !target/ *.properties -
edit Dockerfile.native to add a line that copies *.so files and *.properties:
# Shared objects to be dynamically loaded at runtime as needed, COPY --chown=1001:root target/*.properties target/*.so /work/