CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-com-amazonaws--aws-java-sdk-core

Core foundational library for AWS SDK for Java 1.x providing authentication, HTTP transport, regions, protocols, and shared utilities for all AWS service clients

Pending
Overview
Eval results
Files

waiters.mddocs/

Waiters & Polling

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 Framework

Waiter Interface

// 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

// 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();
}

Polling Strategies

Core Polling Strategy

// 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);
}

Built-in Strategies

// 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 States and Exceptions

Waiter States

// Waiter state enumeration
enum WaiterState {
    SUCCESS,    // Desired state reached
    RETRY,      // Continue polling
    FAILURE;    // Unrecoverable failure
    
    public boolean isSuccess();
    public boolean isFailure();
    public boolean isRetry();
}

Waiter Exceptions

// 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);
}

Waiter Acceptors

Core Acceptor Framework

// 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);
}

Waiter Utilities

SDK Function Interface

// Function interface for SDK operations
interface SdkFunction<Input, Output> {
    Output apply(Input input) throws Exception;
}

Waiter Handler

// 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) {}
}

Waiter Executor Factory

// 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);
}

Usage Examples

Basic Waiter Creation

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();

Using the Waiter

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());
}

Custom Waiter Handler

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());
}

Advanced Polling Strategy

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();

HTTP Status Code Acceptors

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();

Path-Based Acceptors

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;
    }
};

Async Waiter with Custom Executor

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();
    }
}));

Waiter Execution with Direct Control

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");
}

Best Practices

  1. Appropriate Timeouts: Set reasonable maximum retry counts and total wait times to prevent infinite waiting.

  2. Exponential Backoff: Use exponential backoff with jitter for better resource utilization and to avoid overwhelming services.

  3. Error Handling: Implement proper error handling for both timeout and unrecoverable failure scenarios.

  4. Resource Management: Properly shutdown custom executor services to prevent resource leaks.

  5. Logging and Monitoring: Implement comprehensive logging to track waiter progress and diagnose issues.

  6. State Validation: Carefully design acceptor logic to correctly identify success, failure, and retry states.

  7. Performance Considerations: Balance polling frequency with resource consumption and service load.

  8. Async Operations: Use async waiters for long-running operations to avoid blocking application threads.

  9. Custom Handlers: Implement custom waiter handlers for better observability and debugging.

  10. 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

docs

arn-support.md

authentication.md

client-builders.md

endpoint-discovery.md

exception-handling.md

http-transport.md

index.md

metrics-monitoring.md

protocols.md

regions-endpoints.md

retry-policies.md

utilities.md

waiters.md

tile.json