Managing Connections
Edit this article in GitHub
Version 2.3

Managing Connections

This section describes how to connect the Java SDK to a Couchbase cluster and bucket. It contains best practices as well as information on SSL and other advanced connection options.

Connecting to a Bucket

Connecting to a bucket is a two-step process: first, a CouchbaseCluster object needs to be initialized, followed by one or more calls to openBucket():

Cluster cluster = CouchbaseCluster.create();
Bucket bucket = cluster.openBucket();

If no further arguments are applied, this code connects to localhost and opens the default bucket. While this is suitable for development, you most probably want to connect to a remote cluster and also a different bucket (with a password):

Cluster cluster = CouchbaseCluster.create("192.168.56.101", "192.168.56.102");
Bucket bucket = cluster.openBucket("myapp", "password");

Alternatively, a list of nodes can be passed in:

List<String> nodes = Arrays.asList("192.168.56.101", "192.168.56.102");
Cluster cluster = CouchbaseCluster.create(nodes);

The Java SDK also offers limited support for creating a cluster from a Couchbase connection string:

String connectionString = "couchbase://192.168.56.101,192.168.56.102";
CouchbaseCluster cluster = CouchbaseCluster.fromConnectionString(connectionString);
Warning: When creating a cluster from a connection string, the Java SDK ignores the scheme component; if a secure connection is desired, the Couchbase environment must be configured as described in Connecting with SSL. The Java SDK also ignores any options in the connection string, and will not work with a connection string that specifies a bucket name.

It is very important in a production setup to pass in at least two or three nodes of the cluster because if the first one in the list fails the other ones can be tried. After initial contact is made, the bootstrap list provided by the user is thrown away and replaced with a list provided by the server (which contains all nodes of the cluster).

More buckets can be open at the same time if needed:

Bucket bucket1 = cluster.openBucket("bucket1", "password");
Bucket bucket2 = cluster.openBucket("bucket2", "password");

If more than one bucket is open at a time, the underlying core-io library makes sure to utilize the resources as best as possible, that is sharing sockets, thread pools and so on.

Here are some very important things to keep in mind:

  • Always create only one instance of a CouchbaseCluster and share it across threads (same with buckets).
  • The SDK is thread-safe, so no additional synchronization is needed when interacting with the SDK.
  • If different clusters need to be accessed, reuse the CouchbaseEnvironment (See Scalability and Concurrency).

If the underlying environment is not shared the application will continue to work, but a warning is raised in the logs:

WARNING: More than 1 Couchbase Environments found (N), this can have severe
impact on performance and stability. Reuse environments!

In general, this is an indication of misconfiguration, the only exception being unit and integration tests where multiple paths are executed at once and cannot share the ClusterEnvironment for whatever reason.

Disconnecting from a Bucket

The most common case is to disconnect the whole CouchbaseCluster from the server, which has the same effect as closing all buckets manually and in addition close all underlying resources like thread pools. This also means that once disconnect has happened, you can't reopen buckets from the same Cluster instance.

Boolean disconnected = cluster.disconnect();

If the ClusterEnvironment is shared it needs to be closed manually, but if not (which is the regular case) it gets also shut down.

After a disconnect, it is not possible to open buckets again, so only use it when you are sure that you do not need access to a CouchbaseCluster again. If you only want to close a bucket, you can do that without shutting down everything:

Boolean closed = bucket.close();

This will release only the resources allocated for this bucket and it is possible to reopen it at a later point.

All threads used by the SDK are daemon threads, so even if you do not disconnect() manually, your JVM will exit. It is very important that you properly shut down the SDK so that remaining operations are finished and nothing is left behind.

If no timeout is used for the synchronous disconnect() method, the environment disconnect timeout is used, which defaults to 25 seconds. This holds also true if you call close() on a bucket.

Connecting Asynchronously

Every synchronous API is just a wrapper around an asynchronous one. To get to it, you can use one of these methods:

  • Use async() on the interface to access its Async* counterpart.
  • Instantiate a CouchbaseAsyncCluster in the first place.

So if you are connecting to the bucket synchronously but then want to switch over to asynchronous data operations, you can do it like this:

Cluster cluster = CouchbaseCluster.create();
Bucket bucket = cluster.openBucket();

// Same API as Bucket, but completely async with Observables
AsyncBucket asyncBucket = bucket.async();

On the other hand, you can use the Async API right from the beginning:

AsyncCluster cluster = CouchbaseAsyncCluster.create();
Observable<AsyncBucket> bucketObservable = cluster.openBucket();

Scalability and Concurrency

As mentioned previously, you should create only one instance of a CouchbaseCluster and open buckets from there. If you need to connect to multiple clusters though, this is not going to work. To keep things efficient, you should share the CouchbaseEnvironment object between those instances:

CouchbaseEnvironment env = DefaultCouchbaseEnvironment.create();
CouchbaseCluster cluster1 = CouchbaseCluster.create(env, "192.168.56.1");
CouchbaseCluster cluster2 = CouchbaseCluster.create(env, "192.168.57.10");

This ensures that the resources in the environment (thread pools for I/O and computation) are reused. Note that if you pass in your custom environment, it needs to be shut down manually because it's not under the complete control of any cluster. The recommended approach is to first shut down all clusters and after this is done shut down the environment (here using asynchronous code, but you can also loop synchronously):

Observable
    .just(cluster1, cluster2)
    .flatMap(CouchbaseCluster::disconnect)
    .last()
    .flatMap(aBoolean -> env.shutdown())
    .toBlocking()
    .single();

Connecting with SSL

Couchbase Server EE 3.0 and later supports full encryption of client-side traffic. That includes key-value type operations, queries, and configuration communication. Make sure to have a proper Couchbase Server version installed before proceeding with configuring encryption on the client side.

To configure encryption for the Java SDK:

  1. Load and import the certificate from the cluster into your JVM keystore
  2. Enable encryption on the client side and point it to the keystore

The JVM keystore is independent of the Java SDK, so your own setup might look different. It is important to make sure you are transferring the certificate in an encrypted manner from the server to the client side, so either copy it through SSH or through a similar secure mechanism.

If you are running on localhost and just want to enable it for a development machine, just copying and pasting it suffices. Navigate in the admin UI to Settings > Cluster and copy the input box of the SSL certificate into a file on your machine (here named cluster.cert). It looks similar to this:

-----BEGIN CERTIFICATE-----
MIICmDCCAYKgAwIBAgIIE4FSjsc3nyIwCwYJKoZIhvcNAQEFMAwxCjAIBgNVBAMT
ASowHhcNMTMwMTAxMDAwMDAwWhcNNDkxMjMxMjM1OTU5WjAMMQowCAYDVQQDEwEq
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzz2I3Gi1XcOCNRVYwY5R
................................................................
mgDnQI8nw2arBRoseLpF6WNw22CawxHVOlMceQaGOW9gqKNBN948EvJJ55Dhl7qG
BQp8sR0J6BsSc86jItQtK9eQWRg62+/XsgVCmDjrB5owHPz+vZPYhsMWixVhLjPJ
mkzeUUj/kschgQ0BWT+N+pyKAFFafjwFYtD0e5NwFUUBfsOyQtYV9xu3fw+T2N8S
itfGtmmlEfaplVGzGPaG0Eyr53g5g2BgQbi5l5Tt2awqhd22WOVbCalABd9t2IoI
F4+FjEqAEIr1mQepDaNM0gEfVcgd2SzGhC3yhYFBAH//8W4DUot5ciEhoBs=
-----END CERTIFICATE-----

Now, use the keytool command to import it into your JVM keystore.

Note: Use the -keystore option to specify where to create the keystore. If you don't use this option, the default location is the .keystore file in user's home directory.
$ keytool -importcert -file cluster.cert
Enter keystore password:
Owner: CN=*
Issuer: CN=*
Serial number: 1381528ec7379f22
Valid from: Tue Jan 01 01:00:00 CET 2013 until: Sat Jan 01 00:59:59 CET 2050
Certificate fingerprints:
	 MD5:  4A:5E:DB:4F:F6:7E:FD:C3:0E:0C:56:C4:05:34:C1:4A
	 SHA1: 3A:BC:48:3C:0F:36:99:EB:35:76:7C:E5:14:DE:89:DE:AE:79:9B:ED
	 SHA256: 24:46:59:55:F2:65:23:85:E2:80:9F:CC:D1:EF:41:E9:4E:D8:ED:11:C8:CF:60:C7:C5:AD:63:56:D0:E6:7F:4D
	 Signature algorithm name: SHA1withRSA
	 Version: 3
Trust this certificate? [no]:  yes
Certificate was added to keystore

You can verify with keytool -list:

$ keytool -list
Enter keystore password:

Keystore type: JKS
Keystore provider: SUN

Your keystore contains 1 entry

mykey, Aug 18, 2014, trustedCertEntry,
Certificate fingerprint (SHA1): 3A:BC:48:3C:0F:36:99:EB:35:76:7C:E5:14:DE:89:DE:AE:79:9B:ED

The next step is to enable encryption and pass it the path and password of the file.

CouchbaseEnvironment env = DefaultCouchbaseEnvironment
    .builder()
    .sslEnabled(true)
    .sslKeystoreFile("/path/tokeystore")
    .sslKeystorePassword("password")
    .build();

Alternatively, since 2.3.0 it is possible to point it directly to a keystore that has been previously initialized so it can be created from different sources:

KeyStore myKeystore = loadKeystore();

CouchbaseEnvironment env = DefaultCouchbaseEnvironment.builder()
    .sslEnabled(true)
    .sslKeystore(myKeystore)
    .build();

Depending on the OS used there are different default passwords and paths, so consult the JDK manual if you need further information on keytool (Java 7 for Windows and Unix) and the JVM keystore.

There are no other application changes needed. If you want to verify it's actually working ,you can use a tool like tcpdump. For example, an unencrypted upsert request looks like this (using sudo tcpdump -i lo0 -A -s 0 port 11210):

E..e..@.@.............+......q{...#..Y.....
.E...Ey........9........................id{"key":"value"}

After enabling encryption, you cannot inspect the traffic in cleartext (same upsert request, but watched on port 11207 which is the default encrypted port):

E.....@.@.............+....Z.'yZ..#........
..... ...xuG.O=.#.........?.Q)8..D...S.W.4.-#....@7...^.Gk.4.t..C+......6..)}......N..m..o.3...d.,.	...W.....U..
.%v.....4....m*...A.2I.1.&.*,6+..#..#.5

Using DNS SRV records

As an alternative to specifying multiple hosts in your program, you can get the actual bootstrap node list from a DNS SRV record. The following steps are necessary to make it work:

  1. Set up your DNS server to respond properly from a DNS SRV request.
  2. Enable it on the SDK and point it towards the DNS SRV entry.

Your DNS server should be set up like this (one row for each bootstrap node):

_couchbase._tcp.example.com.  3600  IN  SRV  0  0  0  node1.example.com.
_couchbase._tcp.example.com.  3600  IN  SRV  0  0  0  node2.example.com.
_couchbase._tcp.example.com.  3600  IN  SRV  0  0  0  node3.example.com.
Note: The ordering, priorities, ports and weighting are completely ignored and should not be set on the records to avoid ambiguities.
If you plan to use secure connections, you use _couchbases instead:
_couchbases._tcp.example.com.  3600  IN  SRV  0  0  0  node1.example.com.
_couchbases._tcp.example.com.  3600  IN  SRV  0  0  0  node2.example.com.
_couchbases._tcp.example.com.  3600  IN  SRV  0  0  0  node3.example.com. 

DNS SRV bootstrapping is available in the Java SDK from version 2.1.0. In order to make the SDK actually use the SRV records, you need to enable DNS SRV on the environment and pass in the host name from your records (here example.com):

CouchbaseEnvironment env = DefaultCouchbaseEnvironment
    .builder()
    .dnsSrvEnabled(true)
    .build();

Cluster cluster = CouchbaseCluster.create(env, "example.com");

If the DNS SRV records could not be loaded properly you'll get the exception logged and the given host name will be used as a A record lookup.

WARNING: DNS SRV lookup failed, proceeding with normal bootstrap.
javax.naming.NameNotFoundException: DNS name not found [response code 3];
   remaining name '_couchbase._tcp.example.com'
	at com.sun.jndi.dns.DnsClient.checkResponseCode(DnsClient.java:651)
	at com.sun.jndi.dns.DnsClient.isMatchResponse(DnsClient.java:569)

Also, if you pass in more than one node, DNS SRV bootstrap will not be initiated:

INFO: DNS SRV enabled, but less or more than one seed node given.
Proceeding with normal bootstrap.