Java client for Kubernetes and OpenShift providing access to the full Kubernetes & OpenShift REST APIs via a fluent DSL
—
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.
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);
}Exception thrown when a requested resource is not found.
public class ResourceNotFoundException extends KubernetesClientException {
public ResourceNotFoundException(String message);
public ResourceNotFoundException(String message, Throwable cause);
}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();
}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();
}The client maps HTTP status codes to specific exception conditions:
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());
}
}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());
}
}// 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());
}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 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");
}
}
});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);
}
}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());
}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());
}
}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