Managing Connections using the Java SDK with Couchbase Server
This section describes how to connect the Java SDK to a Couchbase cluster. It contains best practices as well as information on TLS/SSL and other advanced connection options.
A connection to a Couchbase Server cluster is represented by a
Cluster provides access to Buckets, Scopes, and Collections, as well as various Couchbase services and management interfaces.
The simplest way to create a
Cluster object is to call
Cluster.connect() with a connection string, username, and password:
Cluster cluster = Cluster.connect("127.0.0.1", "username", "password"); Bucket bucket = cluster.bucket("travel-sample"); Collection collection = bucket.defaultCollection(); // You can access multiple buckets using the same Cluster object. Bucket anotherBucket = cluster.bucket("beer-sample"); // You can access collections other than the default // if your version of Couchbase Server supports this feature. Scope customerA = bucket.scope("customer-a"); Collection widgets = customerA.collection("widgets"); // For a graceful shutdown, disconnect from the cluster when the program ends. cluster.disconnect();
If you are connecting to a version of Couchbase Server older than 6.5, it will be more efficient if the addresses are those of data (KV) nodes.
You will in any case, with 6.0 and earlier, need to open a
In a production environment, your connection string should include the addresses of multiple server nodes in case some are currently unavailable. Multiple addresses may be specified in a connection string by delimiting them with commas:
Cluster cluster = Cluster.connect("192.168.56.101,192.168.56.102", "username", "password");
|You don’t need to include the address of every node in the cluster. The client fetches the full address list from the first node it is able to contact.|
ClusterEnvironment manages shared resources like thread pools, timers, and schedulers.
It also holds the client settings.
One way to customize the client’s behavior is to build your own
ClusterEnvironment with custom settings:
ClusterEnvironment env = ClusterEnvironment.builder() // Customize client settings by calling methods on the builder .build(); // Create a cluster using the environment's custom client settings. Cluster cluster = Cluster.connect("127.0.0.1", ClusterOptions .clusterOptions("username", "password") .environment(env)); // Shut down gracefully. Shut down the environment // after all associated clusters are disconnected. cluster.disconnect(); env.shutdown();
If you create a
A Couchbase connection string is a comma-delimited list of IP addresses and/or hostnames, optionally followed by a list of parameters.
The parameter list is just like the query component of a URI; name-value pairs have an equals sign (
=) separating the name and value, with an ampersand (
&) between each pair.
Just as in a URI, the first parameter is prefixed by a question mark (
The full list of recognized parameters is documented in the client settings reference.
Any client setting with a system property name may also be specified as a connection string parameter (without the
When creating a
A connection string may optionally be prefixed by either
"couchbases://", although the Java SDK completely ignores this prefix if present.
If you wish to use TLS, the
ClusterEnvironment must be configured as described in Secure Connections.
Most of the high-level classes in the Java SDK are designed to be safe for concurrent use by multiple threads.
You will get the best performance if you share and reuse instances of
Collection, all of which are thread-safe.
We recommend creating a single
Cluster instance when your application starts up, and sharing this instance throughout your application.
If you know at startup time which buckets, scopes, and collections your application will use, we recommend obtaining them from the
Cluster at startup time and sharing those instances throughout your application as well.
Before your application stops, gracefully shut down the client by calling the
disconnect() method of each
Cluster you created.
If you created any
ClusterEnvironment instances, call their
shutdown() method after disconnecting the associated clusters.
If a single application needs to connect to multiple Couchbase Server clusters, we recommend creating a single
ClusterEnvironment and sharing it between the
ClusterEnvironment env = ClusterEnvironment.builder() .timeoutConfig(TimeoutConfig.kvTimeout(Duration.ofSeconds(5))) .build(); Cluster clusterA = Cluster.connect( "clusterA.example.com", ClusterOptions.clusterOptions("username", "password") .environment(env)); Cluster clusterB = Cluster.connect( "clusterB.example.com", ClusterOptions.clusterOptions("username", "password") .environment(env)); // ... // For a graceful shutdown, disconnect from the clusters // AND shut down the custom environment when then program ends. clusterA.disconnect(); clusterB.disconnect(); env.shutdown();
When you manually create a
ClusterEnvironment like this, the SDK will not shut it down when you call
Instead you are responsible for shutting it down after disconnecting all clusters that share the environment.
If you create a
If your Couchbase Server cluster is running in a containerized, port mapped, or otherwise NAT’d environment like Docker or Kubernetes, a client running outside that environment may need additional information in order to connect the cluster. Both the client and server require special configuration in this case.
On the server side, each server node must be configured to advertise its external address as well as any custom port mapping.
This is done with the
setting-alternate-address CLI command introduced in Couchbase Server 6.5.
A node configured in this way will advertise two addresses: one for connecting from the same network, and another for connecting from an external network.
On the client side, the externally visible ports must be used when connecting.
If the external ports are not the default, you can specify custom ports using the overloaded
Cluster.connect() method that takes a set of
SeedNode objects instead of a connection string.
int customKvPort = 1234; int customManagerPort = 2345; Set<SeedNode> seedNodes = new HashSet<>(Arrays.asList( SeedNode.create("127.0.0.1", Optional.of(customKvPort), Optional.of(customManagerPort)))); Cluster cluster = Cluster.connect(seedNodes, "username", "password");
|In a deployment that uses multi-dimensional scaling, a custom KV port is only applicable for nodes running the KV service. A custom manager port may be specified regardless of which services are running on the node.|
In many cases the client is able to automatically select the correct set of addresses to use when connecting to a cluster that advertises multiple addresses.
If the detection heuristic fails in your environment, you can override it by setting the
io.networkResolution client setting to
default if the client and server are on the same network, or
external if they’re on different networks.
|Any TLS certificates must be set up at the point where the connections are being made.|
Couchbase Server Enterprise Edition supports full encryption of client-side traffic using Transport Layer Security (TLS). That includes key-value type operations, queries, and configuration communication. Make sure you have the Enterprise Edition of Couchbase Server before proceeding with configuring encryption on the client side.
To configure encryption for the Java SDK:
Get the CA certificate from the cluster and save it in a text file.
Enable encryption on the client side and point it to the file containing the certificate.
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 TLS for a development machine, just copying and pasting it suffices.
Navigate in the admin UI to and copy the input box of the TLS certificate into a file on your machine (which we will refer to as
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-----
The next step is to enable encryption and pass it the path to the certificate file.
ClusterEnvironment env = ClusterEnvironment.builder() .securityConfig(SecurityConfig.enableTls(true) .trustCertificate(Paths.get("/path/to/cluster.cert"))) .build();
Then use this custom
ClusterEnvironment when opening the connection to the cluster.
See Cluster Environment for an example of creating a
Cluster with a custom environment.
If you want to verify it’s actually working, you can use a tool like
For example, an unencrypted upsert request looks like this (using
sudo tcpdump -i lo0 -A -s 0 port 11210):
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
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:
Set up your DNS server to respond properly from a DNS SRV request.
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 11210 node1.example.com. _couchbase._tcp.example.com. 3600 IN SRV 0 0 11210 node2.example.com. _couchbase._tcp.example.com. 3600 IN SRV 0 0 11210 node3.example.com.
|The ordering, priorities, 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._tcp.example.com. 3600 IN SRV 0 0 11207 node1.example.com. _couchbases._tcp.example.com. 3600 IN SRV 0 0 11207 node2.example.com. _couchbases._tcp.example.com. 3600 IN SRV 0 0 11207 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
ClusterEnvironment env = ClusterEnvironment.builder() .ioConfig(IoConfig.enableDnsSrv(true)) .build();
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.
The Couchbase Java SDK provides first-class support for asynchronous and reactive programming. In fact, the synchronous API is just a thin wrapper around the asynchronous API.
Methods in the asynchronous API return instances of the standard
CompletableFuture introduced in Java 8.
The Java SDK’s reactive API is built on Project Reactor.
Reactor implements the standard Reactive Streams API introduced in Java 9, and extends it with a rich set of useful operators.
Methods in the reactive API return instances of
If you wish to embrace the async or reactive programming model, there are two ways to get started:
reactive()on the object to access its
Reactive*counterpart. For example, if you have a
myCollection, you can obtain a
ReactiveClusterin the first place. The
bucket()method of an
AsyncBucket, while the
bucket()method of a
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 = Cluster.connect("127.0.0.1", "username", "password"); Bucket bucket = cluster.bucket("travel-sample"); // Same API as Bucket, but completely async with CompletableFuture AsyncBucket asyncBucket = bucket.async(); // Same API as Bucket, but completely reactive with Flux and Mono ReactiveBucket reactiveBucket = bucket.reactive(); cluster.disconnect();
On the other hand, you can use the Async API right from the beginning:
AsyncCluster cluster = AsyncCluster.connect("127.0.0.1", "username", "password"); AsyncBucket bucket = cluster.bucket("travel-sample"); // An async cluster's disconnect methods returns a CompletableFuture<Void>. // The disconnection starts as soon as you call disconnect(). // The simplest way to wait for the disconnect to complete is to call `join()`. cluster.disconnect().join();
Here’s the same example, but using the Reactive API instead of the Async API:
ReactiveCluster cluster = ReactiveCluster.connect("127.0.0.1", "username", "password"); ReactiveBucket bucket = cluster.bucket("travel-sample"); // A reactive cluster's disconnect methods returns a Mono<Void>. // Nothing actually happens until you subscribe to the Mono. // The simplest way to subscribe is to await completion by calling call `block()`. cluster.disconnect().block();