Calling stored procedures synchronously simplifies the program logic because your client application waits for the procedure to complete before continuing. However, for high performance applications looking to maximize throughput, it is better to queue stored procedure invocations asynchronously.
To invoke stored procedures asynchronously, use the callProcedureAsync() method.
    The procedure call will be queued internally for transmission. The immediate return value is a standard
    Java CompletableFuture object, which will be "completed" when the procedure completes
    (or an error occurs). For example, to invoke a
    NewCustomer() stored procedure asynchronously, the call to callProcedureAsync() might look
    like the following:
CompletableFuture<ClientResponse> future =
    client.callProcedureAsync("NewCustomer",
                              firstname,
                              lastname,
                              custID};To handle the eventual ClientResponse, you can use any of the features of CompletableFuture
    that Java provides. These include awaiting completion with get(), declaring a handler for the
    eventual completion with handle(), and so on.
    
The following are other important points to note when making asynchronous invocations of stored procedures:
Calls to callProcedureAsync() return control to the calling application as soon as the
        procedure call is locally queued.
Errors that occur before the procedure call is queued may be reported via a Java exception. The calling application should include appropriate handling.
Once the procedure is queued, any subsequent error (such as an exception in the stored procedure itself or loss
        of connection to the database) is returned via the CompletableFuture.
There is a limit on the local queue size, after which calls will be rejected. The default queue size
        is 1000 calls, but this can be changed with Client2Config. Robust applications should
        either ensure they can never exceed the local queue size, or implement appropriate handling. You can
        configure a handler to receive notifications when the queue is approaching capacity; see the
        requestBackpressureHandler() method of Client2Config.
If the database server queue is full, transmission is temporarily suspended. This condition,
        known as network backpressure (distinct from request-queue backpressure), is handled internally
        to the Client2 API. This situation
        does not normally happen unless the database cluster is not scaled sufficiently for the workload,
        or there are abnormal spikes in the workload. See Section 6.6.3, “Writing Handlers to Interpret Other Errors” for more information.
The CompletableFuture is "completed normally" when the called procedure has been
    executed on the cluster, and has returned a response. It can also be completed, in this case "exceptionally",
    when an error or timeout occurs.
Normal completion allows access to a ClientResponse structure, the same structure
    that is returned in a synchronous invocation. The ClientResponse contains information
    about the results of execution. In particular, the methods getStatus() and getResults()
    let you determine whether the stored procedure was successful and evaluate the results of the procedure.
Exceptional completion does not have a ClientResponse structure; the exception
    itself conveys the error information.
Completions themselves can be processed synchronously or asynchronously, using the standard facilities of
    CompletableFuture. The VoltDB Java client is single threaded, so synchronous completions are
    processed one at a time. Consequently, it is good practice to keep synchronous completion processing to a minimum,
    returning control to the main thread as soon as possible. If more complex processing is required, use one of
    the available methods for handling the completion asynchronously.