Use execution interceptors in the AWS SDK for Java 2.x
Execution interceptors in the AWS SDK for Java 2.x hook into the request and response lifecycle
to perform custom logic at various stages of API call execution. Use interceptors to implement
cross-cutting concerns such as logging, metrics collection, request modification, debugging, and error handling.
Interceptors implement the
ExecutionInterceptor interface, which provides hooks for different phases of request execution.
Interceptor lifecycle
The ExecutionInterceptor interface provides methods that are called at specific points
during request execution:
beforeExecution - Called before the request executes
modifyRequest - Modifies the SDK request object
beforeMarshalling - Called before the request marshals to HTTP
afterMarshalling - Called after the request marshals to HTTP
modifyHttpRequest - Modifies the HTTP request
beforeTransmission - Called before the HTTP request sends
afterTransmission - Called after the HTTP response is received
modifyHttpResponse - Modifies the HTTP response
beforeUnmarshalling - Called before the HTTP response unmarshals
afterUnmarshalling - Called after the HTTP response unmarshals
modifyResponse - Modifies the SDK response object
afterExecution - Called after successful request execution
onExecutionFailure - Called when request execution fails
Register interceptors
Register interceptors when you build a service client using the overrideConfiguration method.
You can register multiple interceptors, and they execute in the order you register them.
The following class demonstrates how to use execution interceptors to add cross-cutting
concerns like logging, performance monitoring, and request modification to your S3 operations
without changing your core business logic.
This example shows you how to register multiple interceptors on an S3 client and see them
in action during real AWS API calls.
public class S3InterceptorsDemo {
private static final Logger logger = LoggerFactory.getLogger(S3InterceptorsDemo.class);
public static void main(String[] args) {
logger.info("=== AWS SDK for Java v2 - S3 Interceptors Demo ===");
// Create an S3 client with multiple interceptors.
S3Client s3Client = S3Client.builder()
.overrideConfiguration(config -> config
.addExecutionInterceptor(new TimingInterceptor())
.addExecutionInterceptor(new LoggingInterceptor())
.addExecutionInterceptor(new RequestModificationInterceptor()))
.build();
try {
logger.info("Starting S3 operations with interceptors...");
// Operation 1: List buckets.
logger.info("Operation 1: Listing S3 buckets");
logger.info("----------------------------------------");
ListBucketsResponse listBucketsResponse = s3Client.listBuckets();
logger.info("Found {} buckets", listBucketsResponse.buckets().size());
// Operation 2: Try to access a bucket that likely doesn't exist.
logger.info("Operation 2: Checking non-existent bucket (demonstrating error interceptor)");
logger.info("----------------------------------------");
try {
s3Client.headBucket(HeadBucketRequest.builder()
.bucket("amzn-s3-demo-bucket-that-does-not-exist-1234")
.build());
} catch (Exception e) {
logger.info("Expected error occurred (interceptor should have logged it)");
}
} catch (Exception e) {
logger.error(" Error during S3 operations: {}", e.getMessage(), e);
} finally {
s3Client.close();
logger.info("Demo completed - S3 client closed");
}
}
// Logging interceptor.
private static class LoggingInterceptor implements ExecutionInterceptor {
private static final Logger logger = LoggerFactory.getLogger(LoggingInterceptor.class);
@Override
public void beforeExecution(Context.BeforeExecution context, ExecutionAttributes executionAttributes) {
logger.info("[LOGGING] Starting request: {}", context.request().getClass().getSimpleName());
}
@Override
public void afterExecution(Context.AfterExecution context, ExecutionAttributes executionAttributes) {
logger.info("[LOGGING] Completed request: {}", context.request().getClass().getSimpleName());
}
@Override
public void onExecutionFailure(Context.FailedExecution context, ExecutionAttributes executionAttributes) {
logger.error("[LOGGING] Request failed: {}", context.request().getClass().getSimpleName());
if (context.exception() instanceof AwsServiceException) {
AwsServiceException ase = (AwsServiceException) context.exception();
if (ase.awsErrorDetails().errorCode() != null) {
SdkHttpResponse httpResponse = ase.awsErrorDetails().sdkHttpResponse();
logger.error(" HTTP Status: {}", httpResponse.statusCode());
logger.error(" Error Code: {}", ase.awsErrorDetails().errorCode());
logger.error(" Error Message: {}", ase.awsErrorDetails().errorMessage());
}
}
}
}
// Performance timing interceptor.
private static class TimingInterceptor implements ExecutionInterceptor {
private static final Logger logger = LoggerFactory.getLogger(TimingInterceptor.class);
private Instant startTime;
@Override
public void beforeExecution(Context.BeforeExecution context, ExecutionAttributes executionAttributes) {
startTime = Instant.now();
logger.info("⏱️ [TIMING] Request started at: {}", startTime);
}
@Override
public void afterExecution(Context.AfterExecution context, ExecutionAttributes executionAttributes) {
if (startTime != null) {
Duration duration = Duration.between(startTime, Instant.now());
logger.info("⏱️ [TIMING] Request completed in: {}ms", duration.toMillis());
}
}
@Override
public void onExecutionFailure(Context.FailedExecution context, ExecutionAttributes executionAttributes) {
if (startTime != null) {
Duration duration = Duration.between(startTime, Instant.now());
logger.warn("⏱️ [TIMING] Request failed after: {}ms", duration.toMillis());
}
}
}
// Request modification interceptor
private static class RequestModificationInterceptor implements ExecutionInterceptor {
private static final Logger logger = LoggerFactory.getLogger(RequestModificationInterceptor.class);
@Override
public SdkRequest modifyRequest(Context.ModifyRequest context, ExecutionAttributes executionAttributes) {
SdkRequest originalRequest = context.request();
logger.info("[MODIFY] Modifying request: {}", originalRequest.getClass().getSimpleName());
// For ListBucketsRequest, we can't modify much since it has no settable properties
// For HeadBucketRequest, we can demonstrate modifying the request
if (originalRequest instanceof HeadBucketRequest) {
HeadBucketRequest headRequest = (HeadBucketRequest) originalRequest;
// Create a new request with an API name added.
return HeadBucketRequest.builder()
.bucket(headRequest.bucket())
.overrideConfiguration(b -> b.addApiName(ApiName.builder()
.name("My-API")
.version("1.0")
.build()))
.build();
}
logger.info("Not a HeadBucketRequest, returning original request");
return originalRequest;
}
@Override
public SdkHttpRequest modifyHttpRequest(Context.ModifyHttpRequest context, ExecutionAttributes executionAttributes) {
logger.info("[MODIFY] Adding custom HTTP headers");
return context.httpRequest().toBuilder()
.putHeader("X-Custom-Header", "S3InterceptorDemo")
.putHeader("X-Request-ID", java.util.UUID.randomUUID().toString())
.build();
}
}
}
4.0.0org.exampleinterceptors-examples1.0.0jarinterceptors-examplesDemonstration of execution interceptors in AWS SDK for Java v21717UTF-82.31.62org.example.S3InterceptorsDemosoftware.amazon.awssdkbom${aws.java.sdk.version}pomimportorg.apache.logging.log4jlog4j-bom2.23.1pomimportsoftware.amazon.awssdks3org.apache.logging.log4jlog4j-coreorg.slf4jslf4j-api2.0.13org.apache.logging.log4jlog4j-slf4j2-implorg.apache.logging.log4jlog4j-1.2-apiorg.junit.jupiterjunit-jupiter5.10.1testorg.apache.maven.pluginsmaven-compiler-plugin3.11.01717org.apache.maven.pluginsmaven-surefire-plugin3.2.2org.codehaus.mojoexec-maven-plugin3.1.0${exec.mainClass}org.apache.maven.pluginsmaven-shade-plugin3.4.1packageshade${exec.mainClass}false
Best practices
Keep interceptors lightweight - Interceptors execute for every request,
so avoid heavy computations or blocking operations that could impact performance.
Handle exceptions gracefully - If your interceptor throws an exception,
it causes the entire request to fail. Always use try-catch blocks for potentially failing operations.
Order matters - Interceptors execute in the order you register them.
Consider the dependencies between your interceptors when you register them.
Use ExecutionAttributes for state - If you need to pass data between
different interceptor methods, use ExecutionAttributes rather than instance variables to ensure
thread safety.
Be mindful of sensitive data - When you log requests and responses,
be careful not to log sensitive information such as credentials or personal data.
Context objects
Each interceptor method receives a context object that provides access to request and response information
at different stages of execution:
Context.BeforeExecution - Provides access to the original SDK request
Context.ModifyRequest - Modifies the SDK request
Context.ModifyHttpRequest - Modifies the HTTP request
Context.AfterExecution - Provides access to both request and response
Context.FailedExecution - Provides access to the request and the exception that occurred
Did this page help you? - Yes
Thanks for letting us know we're doing a good job!
If you've got a moment, please tell us what we did right so we can do more of it.
Did this page help you? - No
Thanks for letting us know this page needs work. We're sorry we let you down.
If you've got a moment, please tell us how we can make the documentation better.