Error Handling - FreshPerf/PVE4J GitHub Wiki
Error Handling
This guide covers error handling strategies in PVE4J.
Table of Contents
Exception Types
PVE4J operations can throw two main types of exceptions:
ProxmoxAPIError
The primary exception type for API-related errors.
import fr.freshperf.pve4j.throwable.ProxmoxAPIError;
try {
// API operation
} catch (ProxmoxAPIError e) {
System.err.println("Error message: " + e.getMessage());
System.err.println("HTTP status code: " + e.getStatusCode());
System.err.println("Response body: " + e.getResponseBody());
System.err.println("Request URL: " + e.getUrl());
}
ProxmoxAPIError provides:
getMessage()- Human-readable error message (formatted with status, URL, response)getStatusCode()- HTTP status code (400, 401, 404, 500, etc.) or-1if not availablegetResponseBody()- Full response body from Proxmox API, ornullgetUrl()- The URL that was requested, ornull
InterruptedException
Thrown when a thread is interrupted during an operation.
try {
// Long-running operation
} catch (InterruptedException e) {
System.err.println("Operation was interrupted");
Thread.currentThread().interrupt(); // Restore interrupt status
}
Basic Error Handling
Simple Try-Catch
try {
PveVersion version = proxmox.getVersion().execute();
System.out.println("Version: " + version.getVersion());
} catch (ProxmoxAPIError e) {
System.err.println("API Error: " + e.getMessage());
} catch (InterruptedException e) {
System.err.println("Operation interrupted");
Thread.currentThread().interrupt();
}
Handle Multiple Operations
try {
// Get version
PveVersion version = proxmox.getVersion().execute();
System.out.println("Version: " + version.getVersion());
// Get cluster status
List<PveClusterStatus> status = proxmox.getCluster().getStatus().execute();
System.out.println("Cluster nodes: " + status.size());
} catch (ProxmoxAPIError e) {
System.err.println("Failed to retrieve information: " + e.getMessage());
System.err.println("Status code: " + e.getStatusCode());
} catch (InterruptedException e) {
System.err.println("Operation interrupted");
Thread.currentThread().interrupt();
}
Advanced Error Handling
Error Classification by Status Code
public class ProxmoxErrorHandler {
public void handleError(ProxmoxAPIError e) {
int statusCode = e.getStatusCode();
switch (statusCode) {
case 400 -> handleBadRequest(e);
case 401 -> handleUnauthorized(e);
case 403 -> handleForbidden(e);
case 404 -> handleNotFound(e);
case 500 -> handleServerError(e);
default -> handleGenericError(e);
}
}
private void handleBadRequest(ProxmoxAPIError e) {
System.err.println("Bad Request: Invalid parameters");
System.err.println("Details: " + e.getResponseBody());
}
private void handleUnauthorized(ProxmoxAPIError e) {
System.err.println("Unauthorized: Check your API token");
System.err.println("Token may be expired or invalid");
}
private void handleForbidden(ProxmoxAPIError e) {
System.err.println("Forbidden: Insufficient permissions");
System.err.println("Check user roles and ACL settings");
}
private void handleNotFound(ProxmoxAPIError e) {
System.err.println("Not Found: Resource does not exist");
System.err.println("URL: " + e.getUrl());
}
private void handleServerError(ProxmoxAPIError e) {
System.err.println("Server Error: Proxmox internal error");
System.err.println("This might be temporary, consider retrying");
}
private void handleGenericError(ProxmoxAPIError e) {
System.err.println("Error " + e.getStatusCode() + ": " + e.getMessage());
}
}
Parsing Error Response
public class ErrorParser {
public void parseAndLogError(ProxmoxAPIError e) {
try {
JsonObject response = JsonParser.parseString(e.getResponseBody())
.getAsJsonObject();
if (response.has("errors")) {
JsonObject errors = response.getAsJsonObject("errors");
System.err.println("Validation errors:");
errors.entrySet().forEach(entry ->
System.err.println(" " + entry.getKey() + ": " + entry.getValue()));
}
} catch (Exception parseError) {
// If parsing fails, log the raw response
System.err.println("Raw error response: " + e.getResponseBody());
}
}
}
Contextual Error Messages
public class VMOperations {
public void startVM(Proxmox proxmox, String node, int vmid) {
try {
PveTask task = proxmox.getNodes()
.get(node)
.getQemu()
.get(vmid)
.start()
.waitForCompletion(proxmox)
.execute();
System.out.println("VM " + vmid + " started successfully");
} catch (ProxmoxAPIError e) {
String context = String.format("Failed to start VM %d on node %s", vmid, node);
handleVMError(context, e);
} catch (InterruptedException e) {
System.err.println("VM start operation was interrupted");
Thread.currentThread().interrupt();
}
}
private void handleVMError(String context, ProxmoxAPIError e) {
System.err.println(context);
System.err.println("Error: " + e.getMessage());
if (e.getStatusCode() == 500 && e.getResponseBody().contains("already running")) {
System.out.println("Note: VM appears to be already running");
} else if (e.getStatusCode() == 404) {
System.err.println("VM does not exist or node is incorrect");
} else {
System.err.println("Status code: " + e.getStatusCode());
System.err.println("Response: " + e.getResponseBody());
}
}
}
Common Errors
Authentication Errors (401)
try {
proxmox.getVersion().execute();
} catch (ProxmoxAPIError e) {
if (e.getStatusCode() == 401) {
System.err.println("Authentication failed!");
System.err.println("Verify your API token:");
System.err.println(" 1. Check token format: USER@REALM!TOKENID=SECRET");
System.err.println(" 2. Verify token exists in Proxmox");
System.err.println(" 3. Check token is not expired");
System.err.println(" 4. Ensure token has required permissions");
}
}
Resource Not Found (404)
public boolean vmExists(Proxmox proxmox, String node, int vmid) {
try {
proxmox.getNodes()
.get(node)
.getQemu()
.get(vmid)
.getConfig()
.execute();
return true;
} catch (ProxmoxAPIError e) {
if (e.getStatusCode() == 404) {
return false;
}
throw new RuntimeException("Error checking VM existence", e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Interrupted", e);
}
}
Permission Errors (403)
try {
proxmox.getNodes()
.get("pve-node-01")
.getQemu()
.get(100)
.delete()
.execute();
} catch (ProxmoxAPIError e) {
if (e.getStatusCode() == 403) {
System.err.println("Permission denied!");
System.err.println("The API token does not have permission to delete VMs");
System.err.println("Required role: PVEVMAdmin or Administrator");
}
}
SSL Certificate Errors
try {
Proxmox proxmox = Proxmox.create("pve.example.com", 8006, "token");
proxmox.getVersion().execute();
} catch (ProxmoxAPIError e) {
if (e.getMessage().contains("SSL") || e.getMessage().contains("certificate")) {
System.err.println("SSL Certificate Error!");
System.err.println("Solutions:");
System.err.println(" 1. Install valid SSL certificate on Proxmox");
System.err.println(" 2. Use SecurityConfig.insecure() for self-signed certs (dev only)");
System.err.println(" 3. Import self-signed cert to Java trust store");
}
}
Connection Errors
try {
proxmox.getVersion().execute();
} catch (ProxmoxAPIError e) {
if (e.getMessage().contains("Connection refused") ||
e.getMessage().contains("timeout")) {
System.err.println("Connection Error!");
System.err.println("Check:");
System.err.println(" 1. Proxmox host is reachable");
System.err.println(" 2. Port 8006 is accessible");
System.err.println(" 3. Firewall allows connection");
System.err.println(" 4. Proxmox API service is running");
}
}
Retry Strategies
Built-in Retry
PVE4J has built-in retry support on ProxmoxRequest:
PveVersion version = proxmox.getVersion()
.retry(3)
.retryDelay(Duration.ofSeconds(2))
.execute();
Simple Manual Retry
public <T> T executeWithRetry(Callable<T> operation, int maxAttempts) {
int attempt = 0;
Exception lastException = null;
while (attempt < maxAttempts) {
try {
return operation.call();
} catch (ProxmoxAPIError e) {
lastException = e;
attempt++;
if (attempt < maxAttempts) {
System.err.println("Attempt " + attempt + " failed, retrying...");
try {
Thread.sleep(2000);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new RuntimeException("Interrupted during retry", ie);
}
}
} catch (Exception e) {
throw new RuntimeException("Operation failed", e);
}
}
throw new RuntimeException("Operation failed after " + maxAttempts + " attempts",
lastException);
}
// Usage
PveVersion version = executeWithRetry(() ->
proxmox.getVersion().execute(), 3);
Exponential Backoff
public class RetryHandler {
public <T> T executeWithExponentialBackoff(
Callable<T> operation,
int maxAttempts,
long initialDelayMs) {
int attempt = 0;
long delay = initialDelayMs;
while (attempt < maxAttempts) {
try {
return operation.call();
} catch (ProxmoxAPIError e) {
attempt++;
if (attempt >= maxAttempts) {
throw new RuntimeException("Max retry attempts reached", e);
}
// Only retry on specific errors
if (!isRetryable(e)) {
throw new RuntimeException("Non-retryable error", e);
}
System.err.println("Attempt " + attempt + " failed, waiting " +
delay + "ms before retry...");
try {
Thread.sleep(delay);
delay *= 2; // Exponential backoff
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new RuntimeException("Interrupted", ie);
}
} catch (Exception e) {
throw new RuntimeException("Unexpected error", e);
}
}
throw new RuntimeException("Should never reach here");
}
private boolean isRetryable(ProxmoxAPIError e) {
int status = e.getStatusCode();
return status >= 500 || status == 408 || status == 429;
}
}
Conditional Retry
public class SmartRetry {
public <T> T executeWithConditionalRetry(
Callable<T> operation,
int maxAttempts) {
for (int attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return operation.call();
} catch (ProxmoxAPIError e) {
// Don't retry on authentication or permission errors
if (e.getStatusCode() == 401 || e.getStatusCode() == 403) {
throw new RuntimeException("Authentication/Permission error", e);
}
// Don't retry on not found or bad request
if (e.getStatusCode() == 404 || e.getStatusCode() == 400) {
throw new RuntimeException("Client error", e);
}
// Retry on server errors
if (e.getStatusCode() >= 500 && attempt < maxAttempts) {
System.err.println("Server error, retrying... (attempt " +
attempt + "/" + maxAttempts + ")");
try {
Thread.sleep(2000 * attempt);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new RuntimeException("Interrupted", ie);
}
} else {
throw new RuntimeException("Operation failed", e);
}
} catch (Exception e) {
throw new RuntimeException("Unexpected error", e);
}
}
throw new RuntimeException("Max retries reached");
}
}
Custom Exception for Business Logic
public class VMNotFoundException extends RuntimeException {
private final int vmid;
public VMNotFoundException(int vmid) {
super("VM " + vmid + " not found");
this.vmid = vmid;
}
public int getVmid() {
return vmid;
}
}
public PveQemuConfig getVMConfig(Proxmox proxmox, String node, int vmid) {
try {
return proxmox.getNodes()
.get(node)
.getQemu()
.get(vmid)
.getConfig()
.execute();
} catch (ProxmoxAPIError e) {
if (e.getStatusCode() == 404) {
throw new VMNotFoundException(vmid);
}
throw new RuntimeException("Failed to get VM config", e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Interrupted", e);
}
}
Next Steps
- Task Handling - Handling asynchronous tasks
- Getting Started - Basic error handling examples
- VM Management - Error handling in VM operations