Core foundational library for AWS SDK for Java 1.x providing authentication, HTTP transport, regions, protocols, and shared utilities for all AWS service clients
—
The AWS Java SDK Core provides a comprehensive waiter framework for polling-based state transition waiting, enabling applications to wait for resources to reach desired states with configurable polling strategies and timeout handling.
// Core waiter interface
interface Waiter<Input> {
// Synchronous waiting with default handler
void run(Input input) throws WaiterUnrecoverableException, WaiterTimedOutException;
// Synchronous waiting with custom handler
void run(Input input, WaiterHandler<Input> waiterHandler)
throws WaiterUnrecoverableException, WaiterTimedOutException;
}
// Default waiter implementation
class WaiterImpl<Input, Output> implements Waiter<Input> {
public WaiterImpl(WaiterExecutionBuilder<Input, Output> waiterExecutionBuilder);
public void run(Input input) throws WaiterUnrecoverableException, WaiterTimedOutException;
public void run(Input input, WaiterHandler<Input> waiterHandler)
throws WaiterUnrecoverableException, WaiterTimedOutException;
}
// Waiter builder for creating waiters
class WaiterBuilder<Input, Output> {
public static <Input, Output> WaiterBuilder<Input, Output> newBuilder();
// Configuration methods
public WaiterBuilder<Input, Output> withSdkFunction(SdkFunction<Input, Output> sdkFunction);
public WaiterBuilder<Input, Output> withAcceptors(WaiterAcceptor<Output>... acceptors);
public WaiterBuilder<Input, Output> withAcceptors(List<WaiterAcceptor<Output>> acceptors);
public WaiterBuilder<Input, Output> withDefaultPollingStrategy(PollingStrategy pollingStrategy);
public WaiterBuilder<Input, Output> withExecutorService(ExecutorService executorService);
// Build the waiter
public Waiter<Input> build();
}// Waiter execution context
class WaiterExecution<Input, Output> {
public WaiterExecution(WaiterExecutionBuilder<Input, Output> waiterExecutionBuilder);
public WaiterState execute(Input input) throws Exception;
public WaiterState execute(Input input, WaiterHandler<Input> waiterHandler) throws Exception;
}
// Builder for waiter execution
class WaiterExecutionBuilder<Input, Output> {
public static <Input, Output> WaiterExecutionBuilder<Input, Output> builder();
// Configuration methods
public WaiterExecutionBuilder<Input, Output> sdkFunction(SdkFunction<Input, Output> sdkFunction);
public WaiterExecutionBuilder<Input, Output> acceptors(List<WaiterAcceptor<Output>> acceptors);
public WaiterExecutionBuilder<Input, Output> pollingStrategy(PollingStrategy pollingStrategy);
public WaiterExecutionBuilder<Input, Output> executorService(ExecutorService executorService);
// Build execution context
public WaiterExecution<Input, Output> build();
}// Strategy for polling operations
class PollingStrategy {
public PollingStrategy(RetryStrategy retryStrategy, DelayStrategy delayStrategy);
public RetryStrategy getRetryStrategy();
public DelayStrategy getDelayStrategy();
// Retry strategy interface
interface RetryStrategy {
boolean shouldRetry(PollingStrategyContext pollingStrategyContext);
}
// Delay strategy interface
interface DelayStrategy {
void delay(PollingStrategyContext pollingStrategyContext) throws InterruptedException;
}
}
// Context for polling strategies
class PollingStrategyContext {
public static PollingStrategyContext of(int retriesAttempted, WaiterState previousWaiterState);
public int getRetriesAttempted();
public WaiterState getPreviousWaiterState();
public PollingStrategyContext withRetriesAttempted(int retriesAttempted);
public PollingStrategyContext withPreviousWaiterState(WaiterState previousWaiterState);
}// Maximum attempts retry strategy
class MaxAttemptsRetryStrategy implements PollingStrategy.RetryStrategy {
public MaxAttemptsRetryStrategy(int maxAttempts);
public boolean shouldRetry(PollingStrategyContext pollingStrategyContext);
public int getMaxAttempts();
}
// Fixed delay strategy
class FixedDelayStrategy implements PollingStrategy.DelayStrategy {
public FixedDelayStrategy(int delayInSeconds);
public void delay(PollingStrategyContext pollingStrategyContext) throws InterruptedException;
public int getDelayInSeconds();
}// Waiter state enumeration
enum WaiterState {
SUCCESS, // Desired state reached
RETRY, // Continue polling
FAILURE; // Unrecoverable failure
public boolean isSuccess();
public boolean isFailure();
public boolean isRetry();
}// Exception for waiter timeouts
class WaiterTimedOutException extends Exception {
public WaiterTimedOutException(String message);
public WaiterTimedOutException(String message, Throwable cause);
}
// Exception for unrecoverable waiter errors
class WaiterUnrecoverableException extends Exception {
public WaiterUnrecoverableException(String message);
public WaiterUnrecoverableException(String message, Throwable cause);
}// Base class for waiter acceptors
abstract class WaiterAcceptor<Output> {
public abstract WaiterState matches(Output output);
public abstract WaiterState getState();
// Create acceptor instances
public static <Output> WaiterAcceptor<Output> successOn(Predicate<Output> predicate);
public static <Output> WaiterAcceptor<Output> retryOn(Predicate<Output> predicate);
public static <Output> WaiterAcceptor<Output> failureOn(Predicate<Output> predicate);
}
// HTTP success status acceptor
class HttpSuccessStatusAcceptor<Output> extends WaiterAcceptor<Output> {
public HttpSuccessStatusAcceptor();
public WaiterState matches(Output output);
public WaiterState getState();
}
// HTTP failure status acceptor
class HttpFailureStatusAcceptor<Output> extends WaiterAcceptor<Output> {
public HttpFailureStatusAcceptor();
public WaiterState matches(Output output);
public WaiterState getState();
}
// Path matcher for acceptors
interface AcceptorPathMatcher {
boolean matches(Object objectToMatch);
// Create path matchers
static AcceptorPathMatcher pathAll(AcceptorPathMatcher... matchers);
static AcceptorPathMatcher pathAny(AcceptorPathMatcher... matchers);
static AcceptorPathMatcher path(String path, Object expectedValue);
static AcceptorPathMatcher path(String path, AcceptorPathMatcher nestedMatcher);
}// Function interface for SDK operations
interface SdkFunction<Input, Output> {
Output apply(Input input) throws Exception;
}// Handler for waiter events
interface WaiterHandler<Input> {
void onWaiterSuccess(Input input);
void onWaiterFailure(Input input, Throwable throwable);
void beforeWaiterExecution(Input input);
void beforePolling(Input input, int pollAttempt);
}
// No-operation waiter handler
class NoOpWaiterHandler<Input> implements WaiterHandler<Input> {
public void onWaiterSuccess(Input input) {}
public void onWaiterFailure(Input input, Throwable throwable) {}
public void beforeWaiterExecution(Input input) {}
public void beforePolling(Input input, int pollAttempt) {}
}// Factory for waiter executors
class WaiterExecutorServiceFactory {
public static ExecutorService buildExecutorServiceForWaiter();
public static ExecutorService buildExecutorServiceForWaiter(String threadNamePrefix);
public static ScheduledExecutorService buildScheduledExecutorForWaiter();
public static ScheduledExecutorService buildScheduledExecutorForWaiter(String threadNamePrefix);
}import com.amazonaws.waiters.*;
import java.util.concurrent.ExecutorService;
// Create SDK function for the operation to poll
SdkFunction<MyRequest, MyResponse> sdkFunction = new SdkFunction<MyRequest, MyResponse>() {
@Override
public MyResponse apply(MyRequest request) throws Exception {
// Call your AWS service client method
return myServiceClient.describeResource(request);
}
};
// Create acceptors to define success/failure conditions
WaiterAcceptor<MyResponse> successAcceptor = WaiterAcceptor.successOn(response ->
"ACTIVE".equals(response.getStatus())
);
WaiterAcceptor<MyResponse> failureAcceptor = WaiterAcceptor.failureOn(response ->
"FAILED".equals(response.getStatus())
);
WaiterAcceptor<MyResponse> retryAcceptor = WaiterAcceptor.retryOn(response ->
"PENDING".equals(response.getStatus())
);
// Create polling strategy (max 30 attempts, 10 second intervals)
PollingStrategy pollingStrategy = new PollingStrategy(
new MaxAttemptsRetryStrategy(30),
new FixedDelayStrategy(10)
);
// Build the waiter
Waiter<MyRequest> waiter = WaiterBuilder.<MyRequest, MyResponse>newBuilder()
.withSdkFunction(sdkFunction)
.withAcceptors(successAcceptor, failureAcceptor, retryAcceptor)
.withDefaultPollingStrategy(pollingStrategy)
.build();import com.amazonaws.waiters.*;
MyRequest request = new MyRequest().withResourceId("resource-123");
try {
// Wait with default handler
waiter.run(request);
System.out.println("Resource became active!");
} catch (WaiterTimedOutException e) {
System.err.println("Timed out waiting for resource to become active");
} catch (WaiterUnrecoverableException e) {
System.err.println("Unrecoverable error: " + e.getMessage());
}import com.amazonaws.waiters.*;
// Create custom waiter handler
WaiterHandler<MyRequest> customHandler = new WaiterHandler<MyRequest>() {
@Override
public void onWaiterSuccess(MyRequest input) {
System.out.println("Success! Resource " + input.getResourceId() + " is ready");
}
@Override
public void onWaiterFailure(MyRequest input, Throwable throwable) {
System.err.println("Failed waiting for " + input.getResourceId() + ": " +
throwable.getMessage());
}
@Override
public void beforeWaiterExecution(MyRequest input) {
System.out.println("Starting to wait for resource " + input.getResourceId());
}
@Override
public void beforePolling(MyRequest input, int pollAttempt) {
System.out.println("Poll attempt " + pollAttempt + " for resource " +
input.getResourceId());
}
};
// Use custom handler
try {
waiter.run(request, customHandler);
} catch (Exception e) {
System.err.println("Waiter failed: " + e.getMessage());
}import com.amazonaws.waiters.*;
import java.util.concurrent.TimeUnit;
// Custom retry strategy with time-based limits
PollingStrategy.RetryStrategy timeBasedRetry = new PollingStrategy.RetryStrategy() {
private final long startTime = System.currentTimeMillis();
private final long maxWaitTimeMs = TimeUnit.MINUTES.toMillis(10); // 10 minutes max
@Override
public boolean shouldRetry(PollingStrategyContext context) {
long elapsedTime = System.currentTimeMillis() - startTime;
return elapsedTime < maxWaitTimeMs &&
context.getRetriesAttempted() < 100 &&
context.getPreviousWaiterState() == WaiterState.RETRY;
}
};
// Custom delay strategy with exponential backoff
PollingStrategy.DelayStrategy exponentialDelay = new PollingStrategy.DelayStrategy() {
@Override
public void delay(PollingStrategyContext context) throws InterruptedException {
int attempts = context.getRetriesAttempted();
long delayMs = Math.min(1000 * (1L << attempts), 30000); // Cap at 30 seconds
System.out.println("Waiting " + delayMs + "ms before next poll attempt");
Thread.sleep(delayMs);
}
};
// Create advanced polling strategy
PollingStrategy advancedStrategy = new PollingStrategy(timeBasedRetry, exponentialDelay);
// Use in waiter
Waiter<MyRequest> advancedWaiter = WaiterBuilder.<MyRequest, MyResponse>newBuilder()
.withSdkFunction(sdkFunction)
.withAcceptors(successAcceptor, failureAcceptor, retryAcceptor)
.withDefaultPollingStrategy(advancedStrategy)
.build();import com.amazonaws.waiters.*;
import com.amazonaws.http.HttpResponse;
// For operations that return HTTP responses
SdkFunction<MyRequest, HttpResponse> httpFunction = request -> {
// Make HTTP request and return response
return httpClient.execute(request);
};
// HTTP success acceptor (2xx status codes)
WaiterAcceptor<HttpResponse> httpSuccess = new HttpSuccessStatusAcceptor<>();
// HTTP failure acceptor (4xx/5xx status codes)
WaiterAcceptor<HttpResponse> httpFailure = new HttpFailureStatusAcceptor<>();
// Custom status code acceptor
WaiterAcceptor<HttpResponse> customStatusAcceptor = WaiterAcceptor.successOn(response -> {
int statusCode = response.getStatusCode();
return statusCode == 200 || statusCode == 202; // Success on 200 or 202
});
Waiter<MyRequest> httpWaiter = WaiterBuilder.<MyRequest, HttpResponse>newBuilder()
.withSdkFunction(httpFunction)
.withAcceptors(httpSuccess, httpFailure, customStatusAcceptor)
.withDefaultPollingStrategy(pollingStrategy)
.build();import com.amazonaws.waiters.*;
// Acceptor using path matching for complex response structures
WaiterAcceptor<MyResponse> pathBasedAcceptor = new WaiterAcceptor<MyResponse>() {
@Override
public WaiterState matches(MyResponse output) {
// Check nested field values using path matching
if (AcceptorPathMatcher.path("resource.state", "ACTIVE").matches(output)) {
return WaiterState.SUCCESS;
} else if (AcceptorPathMatcher.path("resource.state", "FAILED").matches(output)) {
return WaiterState.FAILURE;
}
return WaiterState.RETRY;
}
@Override
public WaiterState getState() {
return WaiterState.SUCCESS; // This acceptor can return SUCCESS or FAILURE
}
};
// Multiple condition acceptor
WaiterAcceptor<MyResponse> multiConditionAcceptor = new WaiterAcceptor<MyResponse>() {
@Override
public WaiterState matches(MyResponse output) {
// All conditions must match
AcceptorPathMatcher allMatch = AcceptorPathMatcher.pathAll(
AcceptorPathMatcher.path("resource.state", "READY"),
AcceptorPathMatcher.path("resource.healthy", true),
AcceptorPathMatcher.path("resource.endpoints", AcceptorPathMatcher.pathAny(
AcceptorPathMatcher.path("[0].status", "UP"),
AcceptorPathMatcher.path("[1].status", "UP")
))
);
return allMatch.matches(output) ? WaiterState.SUCCESS : WaiterState.RETRY;
}
@Override
public WaiterState getState() {
return WaiterState.SUCCESS;
}
};import com.amazonaws.waiters.*;
import java.util.concurrent.*;
// Create custom executor service
ExecutorService customExecutor = Executors.newFixedThreadPool(5, r -> {
Thread t = new Thread(r, "custom-waiter-thread");
t.setDaemon(true);
return t;
});
// Build waiter with custom executor
Waiter<MyRequest> asyncWaiter = WaiterBuilder.<MyRequest, MyResponse>newBuilder()
.withSdkFunction(sdkFunction)
.withAcceptors(successAcceptor, failureAcceptor, retryAcceptor)
.withDefaultPollingStrategy(pollingStrategy)
.withExecutorService(customExecutor)
.build();
// Use waiter asynchronously
CompletableFuture<Void> waitFuture = CompletableFuture.runAsync(() -> {
try {
asyncWaiter.run(request);
System.out.println("Async wait completed successfully");
} catch (Exception e) {
System.err.println("Async wait failed: " + e.getMessage());
throw new RuntimeException(e);
}
}, customExecutor);
// Handle completion
waitFuture.whenComplete((result, throwable) -> {
if (throwable != null) {
System.err.println("Waiter failed: " + throwable.getMessage());
} else {
System.out.println("Waiter completed successfully");
}
});
// Don't forget to shutdown executor when done
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
customExecutor.shutdown();
try {
if (!customExecutor.awaitTermination(60, TimeUnit.SECONDS)) {
customExecutor.shutdownNow();
}
} catch (InterruptedException e) {
customExecutor.shutdownNow();
}
}));import com.amazonaws.waiters.*;
// Create waiter execution for direct control
WaiterExecution<MyRequest, MyResponse> execution =
WaiterExecutionBuilder.<MyRequest, MyResponse>builder()
.sdkFunction(sdkFunction)
.acceptors(Arrays.asList(successAcceptor, failureAcceptor, retryAcceptor))
.pollingStrategy(pollingStrategy)
.build();
// Manual execution loop
MyRequest request = new MyRequest().withResourceId("resource-123");
int attempts = 0;
WaiterState state = WaiterState.RETRY;
while (state == WaiterState.RETRY && attempts < 30) {
try {
System.out.println("Attempt " + (attempts + 1));
state = execution.execute(request);
if (state == WaiterState.SUCCESS) {
System.out.println("Success after " + (attempts + 1) + " attempts");
break;
} else if (state == WaiterState.FAILURE) {
System.err.println("Failure after " + (attempts + 1) + " attempts");
break;
}
attempts++;
// Manual delay between attempts
if (state == WaiterState.RETRY && attempts < 30) {
Thread.sleep(5000); // 5 second delay
}
} catch (Exception e) {
System.err.println("Exception during wait: " + e.getMessage());
break;
}
}
if (state == WaiterState.RETRY) {
System.err.println("Timed out after " + attempts + " attempts");
}Appropriate Timeouts: Set reasonable maximum retry counts and total wait times to prevent infinite waiting.
Exponential Backoff: Use exponential backoff with jitter for better resource utilization and to avoid overwhelming services.
Error Handling: Implement proper error handling for both timeout and unrecoverable failure scenarios.
Resource Management: Properly shutdown custom executor services to prevent resource leaks.
Logging and Monitoring: Implement comprehensive logging to track waiter progress and diagnose issues.
State Validation: Carefully design acceptor logic to correctly identify success, failure, and retry states.
Performance Considerations: Balance polling frequency with resource consumption and service load.
Async Operations: Use async waiters for long-running operations to avoid blocking application threads.
Custom Handlers: Implement custom waiter handlers for better observability and debugging.
Graceful Degradation: Have fallback strategies when waiters timeout or fail.
The waiter framework provides comprehensive polling capabilities that enable applications to efficiently wait for AWS resources to reach desired states while maintaining proper resource management and error handling.
Install with Tessl CLI
npx tessl i tessl/maven-com-amazonaws--aws-java-sdk-core