Java SDK

This topic describes how to install, configure, and use the Oracle Cloud Infrastructure Java SDK.

  • Services supported:

    • Audit
    • Container Engine for Kubernetes
    • Core Services (Networking, Compute, Block Volume)
    • Database
    • DNS
    • Email Delivery
    • File Storage
    • IAM
    • Load Balancing
    • Object Storage
    • Search
  • Licensing: This SDK and sample is dual licensed under the Universal Permissive License 1.0 and the Apache License 2.0; third-party content is separately licensed as described in the code.
  • Download: GitHub
  • API reference documentation: Java SDK API Reference

Requirements

To use the Java SDK, you must have the following:

  • An Oracle Cloud Infrastructure account.
  • A user created in that account, in a group with a policy that grants the desired permissions. This can be a user for yourself, or another person/system that needs to call the API. For an example of how to set up a new user, group, compartment, and policy, see Adding Users. For a list of typical policies you may want to use, see Common Policies.
  • A key pair used for signing API requests, with the public key uploaded to Oracle. Only the user calling the API should be in possession of the private key. See Configuring the SDK below.
  • Java 8 (for Java 7, see Java 7 Compatibility).
  • A TTL value of 60. For more information, see Configuring JVM TTL for DNS Name Lookups.

Java 7 Compatibility

To use Java 7, you must have a version that supports TLS 1.2.

For more information, see:

Configuring JVM TTL for DNS Name Lookups

The Java Virtual Machine (JVM) caches DNS responses from lookups for a set amount of time, called time-to-live (TTL). This ensures faster response time in code that requires frequent name resolution.

The JVM uses the networkaddress.cache.ttl property to specify the caching policy for DNS name lookups. The value is an integer that represents the number of seconds to cache the successful lookup. The default value for many JVMs, -1, indicates that the lookup should be cached forever.

Because resources in Oracle Cloud Infrastructure use DNS names that can change, we recommend that you change the the TTL value to 60 seconds. This ensures that the new IP address for the resource is returned on next DNS query. You can change this value globally or specifically for your application:

  • To set TTL globally for all applications using the JVM, add the following in the $JAVA_HOME/jre/lib/security/java.security file:

    networkaddress.cache.ttl=60
  • To set TTL only for your application, set the following in your application's initialization code:

    java.security.Security.setProperty("networkaddress.cache.ttl" , "60");

Downloading the SDK

You can download the Java SDK as a zip archive from GitHub. It contains the SDK, all of its dependencies, documentation, and examples. For best compatibility and to avoid issues, use the version of the dependencies included in the archive. Some notable issues are:

  • Bouncy Castle: The SDK bundles 1.52 which automatically supports the use of encrypted PEM keys for authentication.
    • If you need FIPS compliance, you must download and use the FIPS-certified version. The SDK supports bc-fips 1.0.1 and bcpkix-fips 1.0.1. You can download them at: https://www.bouncycastle.org/fips-java/
    • If you need to use encrypted PEM keys with Bouncy Castle 1.53 or later, or with a FIPS-certified version, see Configuring Security Providers.
  • Jax-RS API: The SDK bundles 2.0.1 of the spec. Older versions will cause issues.
  • Jersey Core and Client: The SDK bundles 2.24.1, which is required to support large object uploads to Object Storage. Older versions will not support uploads greater than ~2.1 GB.

Configuring the SDK

The SDK services need two types of configuration: credentials and client-side HTTP settings.

Configuring Credentials

First, you need to set up your credentials and config file. For instructions, see SDK and Tool Configuration.

Next you need to set up the client to use the credentials. The credentials are abstracted through an AuthenticationDetailsProvider interface. Clients can implement this however you choose. We have included a simple POJO/builder class to help with this task (SimpleAuthenticationDetailsProvider).

  • You can load a config with or without a profile:

    ConfigFile config 
        = ConfigFileReader.parse("~/.oci/config");
    ConfigFile configWithProfile 
        = ConfigFileReader.parse("~/.oci/config", "DEFAULT");
  • The private key supplier can be created with the file path directly, or using the config file:

    Supplier<InputStream> privateKeySupplier 
        = new SimplePrivateKeySupplier("~/.oci/oci_api_key.pem");
    Supplier<InputStream> privateKeySupplierFromConfigEntry 
        = new SimplePrivateKeySupplier(config.get("key_file"));
  • To create an auth provider using the builder:

    AuthenticationDetailsProvider provider 
        = SimpleAuthenticationDetailsProvider.builder()
            .tenantId("myTenantId")
            .userId("myUserId")
            .fingerprint("myFingerprint")
            .privateKeySupplier(privateKeySupplier)
            .build();
  • To create an auth provider using the builder with a config file:

    AuthenticationDetailsProvider provider 
        = SimpleAuthenticationDetailsProvider.builder()
            .tenantId(config.get("tenancy"))
            .userId(config.get("user"))
            .fingerprint(config.get("fingerprint"))
            .privateKeySupplier(privateKeySupplier)
            .build();
  • Finally, if you use standard config file keys and the standard config file location, you can simplify this further by using ConfigFileAuthenticationDetailsProvider:

    AuthenticationDetailsProvider provider 
        = new ConfigFileAuthenticationDetailsProvider("ADMIN_USER");

Configuring Client-side Options

Create a client-side configuration through the ClientConfiguration class. If you do not provide your own configuration, the Java SDK uses a default configuration. To provide your own configuration, use the following:

ClientConfiguration clientConfig 
    = ClientConfiguration.builder()
        .connectionTimeoutMillis(3000)
        .readTimeoutMillis(60000)
        .build();

After you have both a credential configuration and the optional client configuration, you can start creating service instances.

Configuring Custom Options

In the config file, you can insert custom key-value pairs that you define, and then reference them as necessary. For example, you could specify a frequently used compartment ID in the config file like so (highlighted in red italics):

[DEFAULT]
user=ocid1.user.oc1..aaaaaaaat5nvwcna5j6aqzjcmdy5eqbb6qt2jvpkanghtgdaqedqw3rynjq
fingerprint=20:3b:97:13:55:1c:5b:0d:d3:37:d8:50:4e:c5:3a:34
key_file=~/.oci/oci_api_key.pem
tenancy=ocid1.tenancy.oc1..aaaaaaaaba3pv6wkcr4jqae5f15p2bcmdyt2j6rx32uzr4h25vqstifsfdsq
custom_compartment_id=ocid1.compartment.oc1..aaaaaaaayzfqeibduyox6iib3olcmdar3ugly4fmameq4h7lcdlihrvur7xq

Then you can retrieve the value like so:

ConfigFile config 
    = ConfigFileReader.parse("~/.oci/config");

String compartmentId = config.get("custom_compartment_id");

Configuring Security Manager Permissions

If your application needs to run inside the Java Security Manager, you must grant additional permissions by updating a policy file, or by specifying an additional or a different policy file at runtime.

The SDK requires the following permissions:

  • Required by Jersey:

    
    permission java.lang.RuntimePermission "getClassLoader";
    permission java.lang.reflect.ReflectPermission "suppressAccessChecks";
    permission java.lang.RuntimePermission "accessDeclaredMembers";
    permission java.util.PropertyPermission "*", "read,write";
    permission java.lang.RuntimePermission "setFactory";
  • Required by the SDK to overwrite reserved headers:

    permission java.util.PropertyPermission "sun.net.http.allowRestrictedHeaders", "write";
  • Required by the SDK to open socket connections:

    permission java.net.SocketPermission "*", "connect";

To include another policy file, in addition to Java Runtime Environment's default policy file, launch the Java Virtual Machine with:

java -Djava.security.manager -Djava.security.policy=</path/to/other_policy>

To replace the default policy file, launch the Java Virtual Machine with:

java -Djava.security.manager -Djava.security.policy==</path/to/other_policy>

Use a single equals sign (=) when supplying an additional policy file. Use a double equals sign (==) only if you wish to replace the default policy file.

Configuring Security Providers

If you need to use password-protected PEM files with a version of Bouncy Castle other than 1.52, you must add the appropriate security provider before using the SDK.

  • For Bouncy Castle 1.53 or later, use the following:
    Security.addProvider(new BouncyCastleProvider());
  • For a FIPS-certified version of Bouncy Castle, use the following:
    Security.addProvider(new BouncyCastleFipsProvider());

Raw Requests

Raw requests are useful, and in some cases necessary. Typical use cases are: when using your own HTTP client, making a OCI-authenticated request to an alternate endpoint, and making a request to a OCI API that is not currently supported in the SDK. The Java SDK exposes the DefaultRequestSigner class that you can use to create a RequestSigner instance for non-standard requests.

The Raw Request example on GitHub shows how to:

  • create an authentication provider and request signer
  • integrate with an HTTP client (Jersey in this example) to authenticate requests

Setting the Endpoints

Service endpoints can be set in one of two ways.

  • Call setEndpoint() on the service instance. This lets you specify a full host name (for example, https://www.example.com).
  • Call setRegion() on the service instance. This selects the appropriate host name for the service for the given region. However, if the service is not supported in the region you set, the Java SDK returns an error.

Note that a service instance cannot be used to communicate with different regions. If you need to make requests to different regions, create multiple service instances.

Apache Connector Add-On

The oci-java-sdk-addons-apache is an optional add-on to the Java SDK that allows for configuring a client connection pool and an HTTP proxy. The add-on leverages the Jersey ApacheConnectorProvider instead of the SDK’s default HttpUrlConnectorProvider when making service calls.

Instruction for installing and configuring the Apache Connector add-on are available on GitHub in the Apache Connector Readme.

Forward Compatibility

Some response fields are of type enum. In the future, individual services may return values not covered by existing enums for that field. To address this possibility, every response field of type enum has an additional value named UnknownEnumValue. If a service returns a value that is not recognized by your version of the SDK, then the response field will be set to this value. Please ensure that your code handles the UnknownEnumValue case if you have conditional logic based on an enum.

Making Synchronous Calls

To make synchronous calls, create an instance of the synchronous client. The general pattern for synchronous clients is that for a service named Example, there will be an interface named ExampleService, and the synchronous client implementation will be called ExampleServiceClient. Here's an example of creating an Object Storage client:

AuthenticationDetailsProvider provider = ...;
ObjectStorage clientWithDefaultClientConfig = new ObjectStorageClient(provider);
clientWithDefaultClientConfig.setRegion(Region.US_ASHBURN_1);

ClientConfiguration clientConfig = ...;
ObjectStorage clientWithExplicitClientConfig = new ObjectStorageClient(provider, clientConfig);
clientWithExplicitClientConfig.setRegion(Region.US_ASHBURN_1);

Synchronous calls will block until the response is available. All SDK APIs return a response object (regardless of whether or not the API sends any content back). The response object typically contains at least a request ID that you can use when contacting Oracle support for help on a particular request.

ObjectStorage client = ...;
GetBucketResponse response = client.getBucket(
    GetBucketRequest.builder().namespaceName("myNamespace").bucketName("myBucket").build());
String requestId = response.getOpcRequestId();
Bucket bucket = response.getBucket();
System.out.println(requestId);
System.out.println(bucket.getName());

Making Asynchronous Calls

To make asynchronous calls, create an instance of the asynchronous client. The general pattern for asynchronous clients is that for a service named Example, there will be an interface named ExampleServiceAsync, and the asynchronous client implementation will be called ExampleServiceAsyncClient. Here's an example of creating an Object Storage client:

AuthenticationDetailsProvider provider = ...;
ObjectStorageAsync clientWithDefaultClientConfig = new ObjectStorageAsyncClient(provider);
clientWithDefaultClientConfig.setRegion(Region.US_ASHBURN_1);
 
ClientConfiguration clientConfig = ...;
ObjectStorageAsync clientWithExplicitClientConfig = new ObjectStorageAsyncClient(provider, clientConfig);
clientWithExplicitClientConfig.setRegion(Region.US_ASHBURN_1);

Asynchronous calls will return immediately. You need to provide an AsyncHandler that will be invoked after the call completes either successfully or unsuccessfully:

ObjectStorageAsync client = ...;
 
AsyncHandler<GetBucketRequest, GetBucketResponse> handler = new AsyncHandler<GetBucketRequest, GetBucketResponse>() {
        @Override
        public void onSuccess(GetBucketRequest request, GetBucketResponse response) {
            String requestId = response.getOpcRequestId();
            Bucket bucket = response.getBucket();
            System.out.println(requestId);
            System.out.println(bucket.getName());
        }

        @Override
        public void onError(GetBucketRequest request, Throwable error) {
            error.printStackTrace();
        }
};
 
Future<GetBucketResponse> future = client.getBucket(
        GetBucketRequest.builder().namespaceName("myNamespace").bucketName("myBucket").build(),
        handler);

Paginated Responses

Some APIs return paginated result sets. The Response objects will contain a method to fetch the next page token. If the token is null, there are no more items. If it is not null, you can make an additional request (setting the token on the Request object) to get the next page of responses. Note, some APIs may return a token even if there are no more results, so it's important to also check whether any items were returned and stop if there are none. Here's an example in the Object Storage API:

ObjectStorage client = ...;
 
ListBucketsRequest.Builder builder =
    ListBucketsRequest.builder().namespaceName(namespace);
String nextPageToken = null;
do {
    builder.page(nextPageToken);
    ListBucketsResponse listResponse = client.listBuckets(builder.build());
    List<Bucket> buckets = listResponse.getItems();
    // handle buckets
    nextPageToken = listResponse.getOpcNextPage();
} while (nextPageToken != null);

 

In addition to working with page tokens manually, each service client exposes a getPaginators() method. ThegetPaginators() method returns a Paginator object, which contains methods that return objects of type Iterable, which abstracts away the need to manually deal with page tokens.

We support two approaches to using iterable:

  • You can iterate over the Response objects that are returned by the list operation. These are referred to as ResponseIterators, and their methods are suffixed with "ResponseIterator," for example, listUsersResponseIterator.
  • You can iterate over the resources/records that are listed. These are referred to as RecordIterator, and their methods are suffixed with "RecordIterator," for example, listUsersRecordIterator.

Following are examples that illustrate both styles of using the iterator:


/// Response iterator
Iterable<ListUsersResponse> responseIterator = identityClient.getPaginators().listUsersResponseIterator(request);
for (ListUsersResponse response : responseIterator) {
    for (User user : response.getItems()) {
        System.out.println(user);
    }
}

/// Record iterator
Iterable<User> recordIterator = identityClient.getPaginators().listUsersRecordIterator(request);
for (User user : recordIterator) {
    System.out.println(user);
}

Exception Handling

Exceptions are runtime exceptions (unchecked), so they do not show up in method signatures. All APIs can throw a BmcException. The exception will contain information about the underlying HTTP request (i.e., status code or timeout). BmcException also contains a getOpcRequestId method that you can use to obtain the request ID to provide when contacting support.

ObjectStorage client = ...;
try {
    GetBucketResponse response = client.getBucket(
	        GetBucketRequest.builder().namespaceName("myNamespace").bucketName("myBucket").build());
    String requestId = response.getOpcRequestId();
    System.out.println(requestId);
} catch (BmcException e) {
    String requestId = e.getOpcRequestId();
    System.out.println(requestId);
    e.printStackTrace();
}

Polling with Waiters

The SDK offers waiters that allow your code to wait until a specific resource reaches a desired state. A waiter can be invoked in both a blocking or a non-blocking (with asychronous callback) manner, and will wait until either the desired state is reached or a timeout is exceeded. Waiters abstract the polling logic you would otherwise have to write into an easy-to-use single method call.

Waiters are obtained through the service client (client.getWaiters()). Both a Get<Resource>Request and the desired lifecycle state are passed in to the waiters.for<Resource> method. For example:

public static Instance waitForInstanceProvisioningToComplete(  ComputeClient computeClient, String instanceId) throws Exception {
        
    ComputeWaiters waiters = computeClient.getWaiters();
    GetInstanceResponse response = waiters.forInstance(
        GetInstanceRequest.builder().instanceId(instanceId).build(),
        Instance.LifecycleState.Running)
    .execute();
        
    return response.getInstance();
}

Each waiters.for<Resource> method has two versions:

  • One version uses the default polling values. For example:

    waiters.forInstance(GetInstanceRequest, LifecycleState)
  • The other version gives you full control over how long to wait and how much time between polling attempts. For example:

    waiters.forInstance(GetInstanceRequest, LifecycleState, TerminationStrategy, DelayStrategy)

Logging

Logging in the SDK is done through SLF4J. SLF4J is a logging abstraction that allows the use of a user-supplied logging library (e.g., log4j). For more information, see the SLF4J manual.

The following is an example that enables basic logging to standard out. More advanced logging options can be configured by using the log4j binding.

  1. Download the SLF4J Simple binding jar: SLF4J Simple Binding
  2. Add the jar to your classpath (e.g., add it to the /third-party/lib directory of the SDK download)
  3. Add the following VM arg to enable debug level logging (by default, info level is used): -Dorg.slf4j.simpleLogger.defaultLogLevel=debug

Uploading Large Objects

The Object Storage service supports multipart uploads to make large object uploads easier by splitting the large object into parts. The Java SDK supports raw multipart upload operations for advanced use cases, as well as a higher level upload class that uses the multipart upload APIs. Managing Multipart Uploads provides links to the APIs used for multipart upload operations. Higher level multipart uploads are implemented using the UploadManager, which will: split a large object into parts for you, upload the parts in parallel, and then recombine and commit the parts as a single object in storage.

The UploadObject example shows how to use the UploadManager to automatically split an object into parts for upload to simplify interaction with the Object Storage service.

Examples

Examples of SDK usage can be found on GitHub, including:

The examples are also in the downloadable .zip file for the SDK. Examples for older versions of the SDK are in the downloadable .zip for the specific version, available on GitHub.

If you'd like to see another example not already covered, file a GitHub issue.

Putting It All Together

  1. Download the SDK to a directory named oci. See GitHub for the download.
  2. Unzip the SDK into the oci directory. For example: tar -xf oci-java-sdk-dist.zip
  3. Create your configuration file in your home directory (~/.oci/config). See Configuring the SDK.
  4. Use javac to compile one of the previous example classes from the examples directory, ex:

    javac -cp lib/oci-java-sdk-full-<version>.jar:third-party/lib/* examples/ObjectStorageSyncExample.java
  5. You should now have a class file in the examples directory. Run the example:

    java -cp examples:lib/oci-java-sdk-full-<version>.jar:third-party/lib/* ObjectStorageSyncExample

Third-Party Dependencies and Shading

The SDK requires a number of third-party dependencies, which are available in the third-party/lib directory. To use the SDK library lib/oci-java-sdk-full-<version>.jar, all of the third-party dependencies in third-party/lib have to be on the class path.

The SDK also includes a second version of the SDK library, shaded/lib/oci-java-sdk-full-shaded-<version>.jar, which contains most of the third-party dependencies already. Only a few more third-party libraries in shaded/third-party/lib have to be on the class path when you use this version of the SDK library.

These two versions of the SDK library are functionally the same, however the second version, shaded/lib/oci-java-sdk-full-shaded-<version>.jar can simplify dealing with different versions of third-party dependencies. This is because all the dependencies that are included in shaded/lib/oci-java-sdk-full-shaded-<version>.jar were shaded, which means they will not interfere with other versions of themselves you may want to include along with this SDK.

You can use either lib/oci-java-sdk-full-<version>.jar or shaded/lib/oci-java-sdk-full-shaded-<version>.jar, but not both. When using lib/oci-java-sdk-full-<version>.jar, use all the third-party libraries in third-party/lib. When using shaded/lib/oci-java-sdk-full-shaded-<version>.jar, use all the third-party libraries in shaded/third-party/lib.

To use the shaded version of the SDK in the Putting It All Together example, replace the javac commands in steps 4 and 5 with the following:

  • Step 4:

    javac -cp shaded/lib/oci-java-sdk-full-shaded-<version>.jar:shaded/third-party/lib/* examples/ObjectStorageSyncExample.java
  • Step 5:

    java -cp examples:shaded/lib/oci-java-sdk-full-shaded-<version>.jar:shaded/third-party/lib/* ObjectStorageSyncExample

Troubleshooting

This section contains troubleshooting information for the Oracle Cloud Infrastructure Java SDK.

ObjectStorage client does not close connections when client is closed.

Too many file descriptors are opened up, and it takes too long to close existing ones. An exception may look like this:

Caused by: java.io.FileNotFoundException: classes/caspertest.pem (Too many open files)
at java.io.FileInputStream.open0(Native Method)
at java.io.FileInputStream.open(FileInputStream.java:195)
at java.io.FileInputStream.<init>(FileInputStream.java:138)

Use one of the following workarounds to fix this issue.

  • Make this call before creating a client: System.setProperty("http.keepAlive", "false");
  • Use this command line argument when running Java: -Dhttp.keepAlive=false

Serialization errors when making requests or handling responses

If you encounter an UnrecognizedPropertyException error when handling a response to a call against the Java SDK, this indicates that the version of the Jackson library in use does not support a feature that was injected at runtime from another dependency in your application's class path. This happens even if the FAIL_ON_UNKNOWN_PROPERTIES deserialization property is set to false for the configured ObjectMapper.

Solution:

Determine which version of Jackson libraries are referenced in your application’s class path and, if necessary, upgrade to version 2.9.5. For a complete list of Jackson libraries that the Java SDK depends on, please refer to the pom.xml file that is hosted on GitHub.

If you customize a client when instantiated in your application, ensure that you reference the preconfigured ObjectMapperfrom the RestClientFactory using the RestClientFactory#getObjectMapper() method.

An alternative solution is to to use the shaded version of the Java SDK jar file, which includes a bundled version of the Jackson libraries.

Encryption key size errors

By default, the Java SDK can only handle keys of 128 bit or lower key length. Users get "Invalid Key Exception" and "Illegal key size" errors when they use longer keys, such as AES256.

Use one of the following workarounds to fix this issue.

Troubleshooting Service Errors

Any operation resulting in a service error will cause an exception of type com.oracle.bmc.model.BmcException to be thrown by the SDK. For information about common service errors returned by OCI, see API Errors.

Contributions

Got a fix for a bug, or a new feature you'd like to contribute? The SDK is open source and accepting pull requests on GitHub.

Notifications

To be notified when a new version of the Java SDK is released, subscribe to the Atom feed.

Questions or Feedback

Ways to get in touch: