CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-io-fabric8--kubernetes-client

Java client for Kubernetes and OpenShift providing access to the full Kubernetes & OpenShift REST APIs via a fluent DSL

Pending
Overview
Eval results
Files

exception-handling.mddocs/

Exception Handling

The Fabric8 Kubernetes Client provides a comprehensive exception hierarchy for handling various error conditions that can occur during Kubernetes API operations. Understanding these exceptions is crucial for building robust applications.

Core Exception Classes

KubernetesClientException

Main exception class for all Kubernetes client operations.

public class KubernetesClientException extends RuntimeException {
    // Constructors
    public KubernetesClientException(String message);
    public KubernetesClientException(String message, Throwable cause);
    public KubernetesClientException(Throwable cause);
    public KubernetesClientException(Status status);
    public KubernetesClientException(String message, int code, Status status);
    
    // Status information
    public Status getStatus();
    public int getCode();
    public String getFullResourceName();
    
    // Utility methods
    public static KubernetesClientException launderThrowable(Throwable cause);
    public static KubernetesClientException launderThrowable(String message, Throwable cause);
}

ResourceNotFoundException

Exception thrown when a requested resource is not found.

public class ResourceNotFoundException extends KubernetesClientException {
    public ResourceNotFoundException(String message);
    public ResourceNotFoundException(String message, Throwable cause);
}

KubernetesClientTimeoutException

Exception thrown when operations exceed configured timeout values.

public class KubernetesClientTimeoutException extends KubernetesClientException {
    public KubernetesClientTimeoutException(String message);
    public KubernetesClientTimeoutException(String message, long timeout, TimeUnit timeUnit);
    public KubernetesClientTimeoutException(String message, Throwable cause);
    
    public long getTimeout();
    public TimeUnit getTimeUnit();
}

WatcherException

Exception specific to watch operations and informer functionality.

public class WatcherException extends Exception {
    public WatcherException(String message);
    public WatcherException(String message, Throwable cause);
    public WatcherException(Throwable cause);
    
    // Conversion methods
    public KubernetesClientException asClientException();
    
    // HTTP Gone detection
    public boolean isHttpGone();
}

HTTP Status Code Mapping

The client maps HTTP status codes to specific exception conditions:

  • 200-299: Success (no exception)
  • 400: Bad Request - Invalid resource definition or parameters
  • 401: Unauthorized - Authentication required or failed
  • 403: Forbidden - Insufficient permissions for the operation
  • 404: Not Found - Resource or endpoint doesn't exist
  • 409: Conflict - Resource already exists or version conflict
  • 410: Gone - Resource version too old (common in watch operations)
  • 422: Unprocessable Entity - Validation errors
  • 429: Too Many Requests - Rate limiting
  • 500: Internal Server Error - Kubernetes API server error
  • 503: Service Unavailable - API server overloaded or maintenance

Usage Examples

Basic Exception Handling

try {
    Pod pod = client.pods().withName("my-pod").get();
    if (pod != null) {
        System.out.println("Pod found: " + pod.getMetadata().getName());
    } else {
        System.out.println("Pod not found (returned null)");
    }
    
} catch (KubernetesClientException e) {
    System.out.println("Error accessing pod: " + e.getMessage());
    System.out.println("HTTP status code: " + e.getCode());
    
    if (e.getStatus() != null) {
        Status status = e.getStatus();
        System.out.println("API status: " + status.getStatus());
        System.out.println("Reason: " + status.getReason());
        System.out.println("Message: " + status.getMessage());
    }
}

Handling Specific HTTP Status Codes

try {
    // Try to create a resource
    ConfigMap configMap = client.configMaps().create(new ConfigMapBuilder()
        .withNewMetadata()
            .withName("my-config")
        .endMetadata()
        .withData(Map.of("key", "value"))
        .build());
        
} catch (KubernetesClientException e) {
    switch (e.getCode()) {
        case 400:
            System.out.println("Bad request - invalid resource definition: " + e.getMessage());
            break;
        case 401:
            System.out.println("Authentication required: " + e.getMessage());
            break;
        case 403:
            System.out.println("Access denied - insufficient permissions: " + e.getMessage());
            break;
        case 409:
            System.out.println("Resource already exists: " + e.getMessage());
            // Maybe try to update instead
            break;
        case 422:
            System.out.println("Validation error: " + e.getMessage());
            if (e.getStatus() != null && e.getStatus().getDetails() != null) {
                StatusDetails details = e.getStatus().getDetails();
                if (details.getCauses() != null) {
                    for (StatusCause cause : details.getCauses()) {
                        System.out.println("  Field: " + cause.getField() + 
                                         ", Reason: " + cause.getReason() + 
                                         ", Message: " + cause.getMessage());
                    }
                }
            }
            break;
        case 429:
            System.out.println("Rate limited - too many requests: " + e.getMessage());
            // Implement backoff and retry
            break;
        default:
            System.out.println("Unexpected error (HTTP " + e.getCode() + "): " + e.getMessage());
    }
}

ResourceNotFoundException Handling

// Using require() method that throws ResourceNotFoundException
try {
    Pod pod = client.pods().withName("required-pod").require();
    // Pod is guaranteed to exist here
    System.out.println("Required pod found: " + pod.getMetadata().getName());
    
} catch (ResourceNotFoundException e) {
    System.out.println("Required pod not found: " + e.getMessage());
    // Handle missing resource
}

// Alternative approach with explicit null check
Pod pod = client.pods().withName("optional-pod").get();
if (pod == null) {
    System.out.println("Optional pod not found");
} else {
    System.out.println("Optional pod found: " + pod.getMetadata().getName());
}

Timeout Exception Handling

try {
    // Wait for pod to be ready with timeout
    Pod readyPod = client.pods().withName("slow-starting-pod")
        .waitUntilReady(2, TimeUnit.MINUTES);
    
    System.out.println("Pod is ready: " + readyPod.getMetadata().getName());
    
} catch (KubernetesClientTimeoutException e) {
    System.out.println("Timeout waiting for pod to be ready: " + e.getMessage());
    System.out.println("Timeout was: " + e.getTimeout() + " " + e.getTimeUnit());
    
    // Check current pod status
    Pod currentPod = client.pods().withName("slow-starting-pod").get();
    if (currentPod != null) {
        PodStatus status = currentPod.getStatus();
        System.out.println("Current phase: " + status.getPhase());
        
        if (status.getConditions() != null) {
            for (PodCondition condition : status.getConditions()) {
                System.out.println("Condition: " + condition.getType() + 
                                 " = " + condition.getStatus() + 
                                 " (" + condition.getReason() + ")");
            }
        }
    }
    
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();
    System.out.println("Wait interrupted: " + e.getMessage());
}

Watch Exception Handling

Watch watch = client.pods().watch(new Watcher<Pod>() {
    @Override
    public void eventReceived(Action action, Pod pod) {
        try {
            System.out.println(action + ": " + pod.getMetadata().getName());
            // Process the event
            processEvent(action, pod);
            
        } catch (Exception e) {
            System.err.println("Error processing event: " + e.getMessage());
        }
    }
    
    @Override
    public void onClose(WatcherException cause) {
        if (cause != null) {
            System.out.println("Watch closed with error: " + cause.getMessage());
            
            if (cause.isHttpGone()) {
                System.out.println("HTTP 410 Gone - resource version too old");
                System.out.println("Need to restart watch with fresh resource version");
                restartWatch();
            } else {
                // Other error - convert to client exception for detailed info
                KubernetesClientException clientException = cause.asClientException();
                System.out.println("Watch error code: " + clientException.getCode());
                
                // Decide whether to restart based on error type
                if (clientException.getCode() >= 500) {
                    System.out.println("Server error - will retry watch");
                    scheduleWatchRetry();
                } else {
                    System.out.println("Client error - not retrying");
                }
            }
        } else {
            System.out.println("Watch closed normally");
        }
    }
});

Exception Chaining and Root Cause Analysis

try {
    // Complex operation that might have nested exceptions
    performComplexKubernetesOperation();
    
} catch (KubernetesClientException e) {
    System.out.println("Kubernetes operation failed: " + e.getMessage());
    
    // Analyze the exception chain
    Throwable cause = e.getCause();
    while (cause != null) {
        System.out.println("Caused by: " + cause.getClass().getSimpleName() + 
                          ": " + cause.getMessage());
        cause = cause.getCause();
    }
    
    // Use launderThrowable to clean up exception chain
    KubernetesClientException laundered = KubernetesClientException.launderThrowable(e);
    System.out.println("Laundered exception: " + laundered.getMessage());
}

private void performComplexKubernetesOperation() {
    try {
        // Some operation that might throw various exceptions
        client.apps().deployments().withName("my-deployment").scale(5);
        
    } catch (Exception e) {
        // Wrap and re-throw with context
        throw KubernetesClientException.launderThrowable("Failed to scale deployment", e);
    }
}

Retry Logic with Exponential Backoff

public <T> T retryOperation(Supplier<T> operation, int maxRetries) {
    int retries = 0;
    long delay = 1000; // Start with 1 second
    
    while (retries < maxRetries) {
        try {
            return operation.get();
            
        } catch (KubernetesClientException e) {
            retries++;
            
            // Don't retry on client errors (4xx)
            if (e.getCode() >= 400 && e.getCode() < 500 && e.getCode() != 429) {
                throw e;
            }
            
            // Don't retry on the last attempt
            if (retries >= maxRetries) {
                throw e;
            }
            
            System.out.println("Operation failed (attempt " + retries + "/" + maxRetries + 
                             "): " + e.getMessage() + ". Retrying in " + delay + "ms");
            
            try {
                Thread.sleep(delay);
            } catch (InterruptedException ie) {
                Thread.currentThread().interrupt();
                throw new RuntimeException("Retry interrupted", ie);
            }
            
            // Exponential backoff with jitter
            delay = Math.min(delay * 2 + (long)(Math.random() * 1000), 30000);
        }
    }
    
    throw new RuntimeException("Max retries exceeded");
}

// Usage example
try {
    Pod pod = retryOperation(() -> 
        client.pods().withName("unstable-pod").get(), 3);
    
    if (pod != null) {
        System.out.println("Successfully retrieved pod: " + pod.getMetadata().getName());
    }
    
} catch (Exception e) {
    System.out.println("Failed to retrieve pod after retries: " + e.getMessage());
}

Validation Error Handling

try {
    // Create a pod with invalid configuration
    Pod invalidPod = client.pods().create(new PodBuilder()
        .withNewMetadata()
            .withName("invalid-pod-name-with-underscores_here") // Invalid name
        .endMetadata()
        .withNewSpec()
            .addNewContainer()
                .withName("app")
                .withImage("") // Invalid empty image
            .endContainer()
        .endSpec()
        .build());
        
} catch (KubernetesClientException e) {
    if (e.getCode() == 422) { // Unprocessable Entity
        System.out.println("Validation errors occurred:");
        
        Status status = e.getStatus();
        if (status != null && status.getDetails() != null) {
            StatusDetails details = status.getDetails();
            
            System.out.println("Resource: " + details.getKind() + "/" + details.getName());
            
            if (details.getCauses() != null) {
                for (StatusCause cause : details.getCauses()) {
                    System.out.println("  Field '" + cause.getField() + "': " + 
                                     cause.getMessage() + " (reason: " + cause.getReason() + ")");
                }
            }
        }
    } else {
        System.out.println("Other error: " + e.getMessage());
    }
}

Global Exception Handler

public class KubernetesExceptionHandler {
    
    public static void handleException(KubernetesClientException e, String operation) {
        System.err.println("Kubernetes operation failed: " + operation);
        System.err.println("Error: " + e.getMessage());
        System.err.println("HTTP Code: " + e.getCode());
        
        // Log full resource name if available
        if (e.getFullResourceName() != null) {
            System.err.println("Resource: " + e.getFullResourceName());
        }
        
        // Log Kubernetes status if available
        if (e.getStatus() != null) {
            Status status = e.getStatus();
            System.err.println("Status: " + status.getStatus());
            System.err.println("Reason: " + status.getReason());
            
            if (status.getDetails() != null) {
                StatusDetails details = status.getDetails();
                System.err.println("Kind: " + details.getKind());
                System.err.println("Name: " + details.getName());
                System.err.println("Group: " + details.getGroup());
            }
        }
        
        // Log stack trace for debugging
        e.printStackTrace();
    }
    
    public static boolean isRetryable(KubernetesClientException e) {
        int code = e.getCode();
        
        // Retry on server errors and rate limiting
        if (code >= 500 || code == 429) {
            return true;
        }
        
        // Retry on network-related errors
        if (e.getCause() instanceof java.net.SocketTimeoutException ||
            e.getCause() instanceof java.net.ConnectException) {
            return true;
        }
        
        return false;
    }
}

// Usage in application
try {
    Pod pod = client.pods().create(newPod);
} catch (KubernetesClientException e) {
    KubernetesExceptionHandler.handleException(e, "create pod");
    
    if (KubernetesExceptionHandler.isRetryable(e)) {
        // Implement retry logic
        scheduleRetry();
    }
}

Install with Tessl CLI

npx tessl i tessl/maven-io-fabric8--kubernetes-client

docs

api-groups.md

client-configuration.md

core-resources.md

custom-resources.md

exception-handling.md

index.md

pod-operations.md

utilities.md

watch-informers.md

tile.json