Skip to content

Errors

The methods of the client do not throw when a call fails. A refused request, a missing privilege, a node that cannot be reached: each returns a Result, and the code decides what to do.

var result = client.getNodes().get("pve01").getQemu().get(100).getStatus().getStart().vmStart();
if (!result.isSuccessStatusCode()) {
System.out.println("Start failed: " + result.getStatusCode() + " " + result.getReasonPhrase());
return;
}

A call whose Result is not checked fails silently: the code goes on as if the VM had started.

Check True when
isSuccessStatusCode() The HTTP status is 200: Proxmox VE accepted the request
responseInError() The answer has an errors member: one or more parameters were refused. The status is 400
getError() Text of those errors, one parameter per line. Empty when responseInError() is false
getReasonPhrase() The reason of any failure: Proxmox VE writes its error message here

isSuccessStatusCode() is the one to test: it is false for every failure, including those that have responseInError(). Use getError() to show which parameter was wrong:

import java.util.Map;
var result = client.set("/nodes/pve01/qemu/100/config", Map.of("memory", "abc"));
if (!result.isSuccessStatusCode()) {
System.out.println(result.getReasonPhrase()); // Parameter verification failed.
if (result.responseInError()) {
System.out.println(result.getError()); // memory : invalid format - ...
}
}

A request that cannot be completed is also returned as a Result, built by the client, so the same check covers it:

Failure getStatusCode() getReasonPhrase()
Name not resolved, connection refused, certificate refused 0 The class and the message of the exception
Request timed out (the timeout of the client) 408 Request timed out after <n> ms: and the message of the exception
An answer that is not JSON (a proxy page, another service on that port) 502, or the status received if it was already an error The answer is not JSON (HTTP 200): and the start of the body

In all three getData() is null. The exception of a request without an answer is written to the log at SEVERE level: see Troubleshooting.

Status 500 is what Proxmox VE itself returns when it cannot do what was asked, for example for a VM id that does not exist: the reason is in getReasonPhrase().

Call Exception When
waitForTaskToFinish, taskIsRunning, getExitStatusTask PveResultException The status of the task cannot be read, or the value is not a UPID
login PveExceptionAuthentication The user needs a second factor and none was given
setTimeout IllegalArgumentException The timeout is negative
Reading getData().get("member") NullPointerException The call failed, or the member is not in the answer

PveResultException is unchecked and carries the failed call in getResult(). PveExceptionAuthentication is checked, so every call to login has to handle or declare it.

An application that prefers exceptions can turn every failed call into one with a small method of its own:

import it.corsinvest.proxmoxve.api.PveResultException;
import it.corsinvest.proxmoxve.api.Result;
static Result ensureSuccess(Result result) {
if (!result.isSuccessStatusCode()) {
throw new PveResultException(result, result.responseInError()
? result.getError()
: result.getStatusCode() + " " + result.getReasonPhrase());
}
return result;
}
var result = ensureSuccess(client.getNodes().get("pve01").getQemu().get(100).getStatus().getStart().vmStart());

A successful call is not a finished operation

Section titled “A successful call is not a finished operation”

For the operations that run as a task, a successful Result means the task was started. Whether it ended well is in the exit status of the task: see Tasks.

Because failures are results, a retry is a loop on the Result, not a catch. Retry only what is safe to repeat, and only for failures that can pass:

Result result = null;
for (var attempt = 1; attempt <= 3; attempt++) {
result = client.getCluster().getResources().resources();
var status = result.getStatusCode();
var canPass = status == 0 || status == 408 || status >= 500;
if (result.isSuccessStatusCode() || !canPass) {
break;
}
Thread.sleep(attempt * 2000L);
}

The other statuses (wrong parameters, no permission) give the same answer every time: retrying them is useless. A 401 from an expired ticket needs a new login, not a retry.