Manage client lifecycles on Java Cloud Client Libraries

Client library instances are reusable and designed to be long-lived. Typically, applications maintain a single instance of a client library, rather than creating a library for each request.

Using Java-KMS as an example, the following snippet shows multiple requests being invoked with the same client instance:

// Create one instance of KMS's KeyManagementServiceClient
KeyManagementServiceClient keyManagementServiceClient =
 KeyManagementServiceClient.create();
keyManagementServiceClient.listKeyRings();
keyManagementServiceClient.asymmetricSign();
keyManagementServiceClient.createCryptoKey();
// ... other code ...

// Create one instance of KMS's AutokeyClient
AutokeyClient autokeyClient = AutokeyClient.create();
autokeyClient.listKeyHandles();
autokeyClient.getKeyHandle();

Close a client

How you manage a client's lifecycle can depend on the use case and the specific client library. For example, if you're using a framework, follow the framework's guidelines for client management. While some scenarios might use try-with-resources for shorter-lived clients, reusing a long-lived client instance is generally recommended for efficiency

Calling close() on a client attempts an orderly shutdown and ensures that existing tasks continue until completion. The client won't accept new tasks. If you don't close the client, its resources continue to persist, and your application incurs memory leaks.

The following example uses Java-KMS and shows the client being closed:

KeyManagementServiceClient keyManagementServiceClient =
  KeyManagementServiceClient.create(keyManagementServiceSettings);
// ... other code ...
keyManagementServiceClient.close();

// For gRPC clients, it's recommended to call awaitTermination to ensure a
// graceful shutdown and avoid the following error message in the logs:
// ERROR i.g.i.ManagedChannelOrphanWrapper - *~*~*~ Channel ManagedChannelImpl
// was not shutdown properly!!! ~*~*~*
// java.lang.RuntimeException: ManagedChannel allocation site
// at io.grpc.internal.ManagedChannelOrphanWrapper$ManagedChannelReference.<init>
keyManagementServiceClient.awaitTermination(DURATION, TimeUnit.SECONDS);

// Optionally include a shutdownNow() call after awaitTermination() to force
// close any lingering resources
keyManagementServiceClient.shutdownNow();

In addition to close(), some Java client libraries expose a few related methods for managing the client lifecycle:

  • shutdown(): Equivalent to close().
  • shutdownNow(): Invokes the shutdown process immediately. Stops all executing tasks and doesn't wait for the task to complete.
  • isShutdown(): Returns true if the background tasks have been shut down.
  • isTerminated(): Returns true if all tasks have completed following shutdown.
  • awaitTermination(): Blocks for a duration until all work has completed following shutdown.

Cases for multiple clients

There can be specific customer use cases that warrant multiple co-existing instances of a client library. The main use case for having multiple clients is when there are requests that must be sent to multiple different endpoints. A client instance connects to a single endpoint. To connect to multiple endpoints, create a client for each.

Potential multi-client risks

There are a few common risks with applications using multiple client library instances:

  • Increased potential for memory leaks. Resources linger if not all client instances are properly closed.
  • Performance implications. Initializing multiple gRPC channels incurs a performance cost, which can add up in performance-sensitive applications.
  • Issues with in-flight requests. Closing a client while requests are still in-flight can lead to a RejectedExecutionException. Ensure requests complete before closing the client.