Install and Start Using the Scala SDK with Couchbase Server

    +
    The Couchbase Scala SDK enables you to interact with a Couchbase Server cluster from the Scala language.

    Installing the SDK

    The Couchbase Scala SDK is available on the Maven repository, packaged for Scala 2.12 and 2.13. The latest version, as of April 2022, is 1.2.7.

    With SBT Projects

    It can be included in your SBT build like this:

    libraryDependencies += "com.couchbase.client" %% "scala-client" % "1.2.7"

    This will automatically use the Scala 2.12 or 2.13 builds, as appropriate for your SBT project.

    With Gradle Projects

    It can be included in your build.gradle like this for 2.12:

    dependencies {
        compile group: 'com.couchbase.client', name: 'scala-client_2.12', version: '1.2.7'
    }

    or 2.13:

    dependencies {
        compile group: 'com.couchbase.client', name: 'scala-client_2.13', version: '1.2.7'
    }

    With Maven Projects

    It can be included in your Maven pom.xml like this for 2.12:

    <dependencies>
        <dependency>
            <groupId>com.couchbase.client</groupId>
            <artifactId>scala-client_2.12</artifactId>
            <version>1.2.7</version>
        </dependency>
    </dependencies>

    or 2.13:

    <dependencies>
        <dependency>
            <groupId>com.couchbase.client</groupId>
            <artifactId>scala-client_2.13</artifactId>
            <version>1.2.7</version>
        </dependency>
    </dependencies>

    Connecting to a Cluster

    Now you have the Scala client installed, try out the following to connect to a Couchbase cluster.

    First pull in all the imports we’ll be using:

    import com.couchbase.client.scala.durability.Durability
    import com.couchbase.client.scala.env.{ClusterEnvironment, SecurityConfig}
    import com.couchbase.client.scala.json.{JsonObject, JsonObjectSafe}
    import com.couchbase.client.scala.kv.ReplaceOptions
    import com.couchbase.client.scala.{Cluster, ClusterOptions}
    import io.netty.handler.ssl.util.InsecureTrustManagerFactory
    
    import java.nio.file.Path
    import java.util.UUID
    import scala.concurrent.duration._
    import scala.util.{Failure, Success, Try}

    Now we can connect to the cluster:

    • Local Couchbase Server

    • Couchbase Capella

    val cluster = Cluster.connect("10.112.180.101", "username", "password").get

    Of course, you’ll need to change the IP address to match your own cluster’s.

    You connect to a Couchbase Capella cluster the same as any other cluster, except that the use of TLS and a certificate is mandatory, and the "couchbases://" connection string prefix should be used to allow DNS SRV lookup.

    If you are connecting to Capella rather than a local Couchbase Server, then also refer to the Cloud section, below.

    val env: ClusterEnvironment = ClusterEnvironment.builder
      .securityConfig(SecurityConfig()
        .enableTls(true)
        .trustCertificate(Path.of("/path/to/cluster-root-certificate.pem")))
      .build
      .get
    
    val cluster: Cluster = Cluster.connect("couchbases://428ecdea-c7ca-4a7e-82c6-ef8d927cda0a.dp.cloud.couchbase.com",
      ClusterOptions.create("username", "password")
        .environment(env))
      .get
    Cluster.connect returns a Try[Cluster], as the Scala client uses functional error handling and does not throw exceptions. You’ll see examples later of how to better handle a Try, but for simplicity here we’ll assume the operation succeeded and get the result as a Cluster using .get.

    Couchbase uses Role Based Access Control (RBAC) to control access to resources, so specify the username and password of a user you’ve setup during installation of the Couchbase Data Platform.

    Now we can open a Couchbase bucket, and its default collection:

    val bucket = cluster.bucket("bucket-name")
    bucket.waitUntilReady(30.seconds).get
    val collection = bucket.defaultCollection

    The Scala SDK supports the new features of scopes and collections in Couchbase Server 7.0. These allow documents to be grouped in a more granular way than buckets.

    If you do not refer to a named collection, you can access the 'default collection', which includes all documents in a bucket, and is forwards and backwards compatible with all supported versions of Couchbase Server.

    waitUntilReady is an optional call. Opening resources such as buckets is asynchronous — that is, the cluster.bucket call returns immediately and proceeds in the background. waitUntilReady ensures that the bucket resource is fully loaded before proceeding. If not present, then the first Key Value (KV) operation on the bucket will wait for it to be ready. As with Cluster.connect, we use .get on the result here for simplicity: see the Managing Connections page for a more complete example including functional error-handling.

    JSON

    Now we can do some simple Key Value operations. First, let’s create some JSON.

    The Scala SDK directly supports several popular JSON libraries, including uPickle/uJson, Circe, Play Json, Jawn, and Json4s (if you’d like to see your favourite supported, please let us know). In addition you can supply JSON encoded into a String or Array[Byte], opening the door to any JSON library; Jsoniter and Jackson have been tested this way, but any should work.

    You can also directly encode and decode Scala case classes to and from the SDK.

    To make things easy and to help get you started, the Scala SDK also bundles a home-grown small JSON library, which you are free to use instead of or alongside any of the other supported JSON libraries. The philosophy behind this library is to provide a very easy-to-use API and the fastest JSON implementation possible.

    These options are described in detail here, but to get us started let’s created some simple JSON using the built-in JsonObject library:

    val json = JsonObject("status" -> "awesome")

    Key-Value Operations

    And now let’s upsert it into Couchbase (upsert is an operation that will insert the document if it does not exist, or replace it if it does). We need to provide a unique ID for the JSON, and we’ll use a UUID here:

    val docId = UUID.randomUUID().toString
    collection.upsert(docId, json) match {
      case Success(result)    =>
      case Failure(exception) => println("Error: " + exception)
    }

    As mentioned above, the Scala SDK will not throw exceptions. Instead, methods that can error - such as the upsert above - will return a Scala Try result, which can either be a Success containing the result, or a Failure containing a Throwable exception. The easiest way to handle a single operation is with pattern matching, as shown above.

    Now let’s get the data back (this example will look a little messy due the the nested handling of multiple Try results, but we’ll see how to clean it up shortly):

    // Get a document
    collection.get(docId) match {
      case Success(result) =>
        // Convert the content to a JsonObjectSafe
        result.contentAs[JsonObjectSafe] match {
          case Success(json) =>
            // Pull out the JSON's status field, if it exists
            json.str("status") match {
              case Success(hello) => println(s"Couchbase is $hello")
              case _              => println("Field 'status' did not exist")
            }
          case Failure(err) => println("Error decoding result: " + err)
        }
      case Failure(err) => println("Error getting document: " + err)
    }

    Here we’re fetching the value for the key docId, converting that value to a JsonObjectSafe (a simple wrapper around JsonObject that returns Try results - see here for details), and then accessing the value of the status key as a String.

    Better Error Handling

    All three of these operations could fail, so there’s quite a lot of error handling code here to do something quite simple. One way to improve on this is by using flatMap, like this:

    val result: Try[String] = collection
      .get(docId)
      .flatMap(_.contentAs[JsonObjectSafe])
      .flatMap(_.str("status"))
    
    result match {
      case Success(status) => println(s"Couchbase is $status")
      case Failure(err)    => println("Error: " + err)
    }

    Alternatively, you can use a for-comprehension, like so:

    val result: Try[String] = for {
      result <- collection.get(docId)
      json <- result.contentAs[JsonObjectSafe]
      status <- json.str("status")
    } yield status
    
    result match {
      case Success(status) => println(s"Couchbase is $status")
      case Failure(err)    => println("Error: " + err)
    }

    Either of these methods will stop on the first failed operation. So the final returned Try contains either a) Success and the result of the final operation, indicating that everything was successful, or b) Failure with the error returned by the first failing operation.

    Overloads

    You’ll notice that most operations in the Scala SDK have two overloads. One will take an Options builder, which provides all possible options that operation takes. For instance:

    collection
      .replace(docId, json, ReplaceOptions()
        .expiry(10.seconds)
        .durability(Durability.Majority)) match {
      case Success(status) =>
      case Failure(err)    => println("Error: " + err)
    }

    These options blocks are implemented as Scala case classes, e.g. they are immutable data objects that return a copy of themselves on each change.

    The other overload is provided purely for convenience. It takes named arguments instead of an Options object, and provides only the most commonly used options:

    collection.replace(docId, json, durability = Durability.Majority) match {
      case Success(status) =>
      case Failure(err)    => println("Error: " + err)
    }

    Cloud Connections

    For developing on Couchbase Capella, if you are not working from the same Availability Zone as your Capella instance, refer to the following:

    Next Steps

    You now know the basics of connecting to a Couchbase cluster, creating some JSON, and performing Key-Value operations, using the Scala SDK.

    Key-Value operations are described in detail here.

    For performing operations against multiple documents, check out how to use N1QL in Scala here.

    The Scala SDK includes three APIs. The examples above show the simple blocking API, for simplicity, but you can also perform all operations in an async style using Scala Future, and a reactive style using Project Reactor SMono and SFlux. Please see Choosing an API for more details.

    API reference are available in a scaladocs jar alongside the release.