JVM drivers (Java, Scala)

Java logo Scala logo

Fauna’s open source JVM driver supports languages that run in the Java Virtual Machine. Currently, Java and Scala clients are implemented.

Current stable version

4.5.0

Repository

Features

Driver API documentation

Javadocs and Scaladocs are hosted on GitHub:

Detailed documentation is available for each language:

Dependencies

Shared

Java

  • Java 11

Scala

  • Scala 2.11.x

  • Scala 2.12.x

Install

Java

Download from the Maven central repository:

faunadb-java/pom.xml:
<dependencies>
  ...
  <dependency>
    <groupId>com.faunadb</groupId>
    <artifactId>faunadb-java</artifactId>
    <version>4.3.0</version>
    <scope>compile</scope>
  </dependency>
  ...
</dependencies>

Scala

faunadb-scala/sbt:
libraryDependencies += ("com.faunadb" %% "faunadb-scala" % "4.3.0")

Usage

Java

import com.faunadb.client.FaunaClient;

import static com.faunadb.client.query.Language.*;

/**
 * This example connects to FaunaDB using the secret provided
 * and creates a new database named "my-first-database"
 */
public class Main {
    public static void main(String[] args) throws Exception {

        //Create an admin connection to FaunaDB.
        FaunaClient adminClient =
            FaunaClient.builder()
                .withSecret("YOUR_FAUNA_SECRET")
                .withEndpoint("https://db.fauna.com/") // Adjust for Region Groups
                .build();

        adminClient.query(
            CreateDatabase(
                Obj("name", Value("my-first-database"))
            )
        ).get();

        adminClient.close();
    }
}

For more usage details, see the supplemental Java driver documentation, and the Connections page.

Scala

import faunadb._
import faunadb.query._
import scala.concurrent._
import scala.concurrent.duration._

/**
  * This example connects to FaunaDB using the secret provided
  * and creates a new database named "my-first-database"
  */
object Main extends App {

  import ExecutionContext.Implicits._

  val client = FaunaClient(
    secret = "YOUR_FAUNA_SECRET",
    endpoint = "https://db.fauna.com" // Adjust for Region Groups
  )

  val result = client.query(
    CreateDatabase(
      Obj("name" -> "my-first-database")
    )
  )

  Await.result(result, Duration.Inf)

  client.close()
}

For more usage details, see the supplemental Scala driver documentation, and the Connections page.

Per-query metrics

There are several metrics that are returned with each request in the response headers. See per-query metrics for more details.

You can access the metrics by using client.queryWithMetrics in place of client.query:

Java

import com.faunadb.client.FaunaClient;

import static com.faunadb.client.query.Language.*;

public class Main {
    public static void main(String[] args) throws Exception {

        FaunaClient client =
            FaunaClient.builder()
                .withSecret("YOUR_FAUNA_SECRET")
                .withEndpoint("https://db.fauna.com") // Adjust for Region Groups
                .build();

        MetricsResponse metricsResponse = client.queryWithMetrics(
            Paginate(Match(Index("spells_by_element"), Value("fire"))),
            Optional.empty()
        ).get();

        // the result of the query, as if you used 'client.query' instead
        Value value = metricsResponse.getValue();

        // get the value of 'x-byte-read-ops' metric
        Optional<String> byteReadOps = metricsResponse.getMetric(
          MetricsResponse.Metrics.BYTE_READ_OPS
        );

        // get the value of 'x-byte-write-ops' metric
        Optional<String> byteWriteOps = metricsResponse.getMetric(
          MetricsResponse.Metrics.BYTE_WRITE_OPS
        );

        // you can get other metrics in the same way,
        // all of them are exposed via MetricsResponse.Metrics enum
    }
}

Scala

import faunadb._
import faunadb.query._
import scala.concurrent._
import scala.concurrent.duration._

/**
  * This example connects to Fauna using the secret provided
  * and creates a new database named "my-first-database"
  */
object Main extends App {

  import ExecutionContext.Implicits._

  val client = FaunaClient(
    secret = "YOUR_FAUNA_SECRET",
    endpoint = "https://db.fauna.com" // Adjust for Region Groups
  )

  val metricsResponse = client.queryWithMetrics(
    Paginate(Match(Index("spells_by_element"), Value("fire"))),
    None
  ).futureValue

  // the result of the query, as if you used 'client.query' instead
  val value = metricsResponse.value

  // get the value of 'x-byte-read-ops' metric
  val byteReadOps = metricsResponse.getMetric(Metrics.ByteReadOps)

  // get the value of 'x-byte-write-ops' metric
  val byteWriteOps = metricsResponse.getMetric(Metrics.ByteWriteOps)

  // you can get other metrics in the same way,
  // all of them are exposed via Metrics enum
}

Custom headers

You can provide custom headers to send with each HTTP request. These headers are defined in the client constructor:

Java

FaunaClient client =
    FaunaClient.builder()
        .withSecret("YOUR_FAUNA_SECRET")
        .withEndpoint("https://db.fauna.com") // Adjust for Region Groups
        .withCustomHeaders(
            Map.of(
                "custom-header-1", "value-1",
                "custom-header-2", "value-2"
            )
        )
        .build();

Scala

val client = FaunaClient(
  secret = "YOUR_FAUNA_SECRET",
  endpoint = "https://db.fauna.com", // Adjust for Region Groups
  customHeaders = scala.Predef.Map(
    "custom-header-1" -> "value-1",
    "custom-header-2" -> "value-2"
  )
)

Event streaming

This section demonstrates how to subscribe to change events.

There are two kinds of event streaming:

The code required to subscribe to each type is very similar. The primary difference is the type of Reference involved in the subscription, and the kinds of events that are included in the stream.

There is a cost in compute operations to hold a stream open, or to repeatedly start a stream that fails.

See Billing for details.

Document streaming

Java

The streaming API is built using the java.util.concurrent.Flow API, which enables users to establish flow-controlled components in which Publishers produce items consumed by one or more Subscribers, each managed by a Subscription.

The following example subscribes to change events for a specific document:

package example;

import static com.faunadb.client.query.Language.*;
import static com.faunadb.client.query.Language.TimeUnit.*;
import static com.faunadb.client.types.Codec.*;
import static com.faunadb.client.types.Value.NullV.NULL;
import com.faunadb.client.query.Expr;

import com.faunadb.client.FaunaClient;
import com.faunadb.client.types.*;
import com.faunadb.client.types.Value.*;

import java.util.Map;
import java.util.*;
import static java.util.Arrays.asList;

import java.util.concurrent.Flow;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * The class for our example app code.
 */
public class App {

    /**
     * The example.
     */
    public static void main(final String[] args) throws Exception {
        String secret   = System.getenv("FAUNADB_SECRET");
        String endpoint = System.getenv("FAUNADB_ENDPOINT");

        if (secret == null || secret == "") {
            System.out.println(
                "The FAUNADB_SECRET environment variable is not set, exiting."
            );
            System.exit(1);
        }
        if (endpoint == null || endpoint == "") {
            endpoint = "https://db.fauna.com/";
        }

        FaunaClient client = FaunaClient.builder()
            .withSecret(secret)
            .withEndpoint(endpoint)
            .build();

        // Define a reference to the document that we want to stream
        // Note that the Scores collection must already exist
        Expr docRef = Ref(Collection("Scores"), "1");

        // Setup a Flow publisher to process the stream
        Flow.Publisher<Value> valuePublisher = client.stream(docRef).get();

        // Setup a future so that we can manage the stream's future
        // events
        CompletableFuture<List<Value>> capturedEvents = new CompletableFuture<>();

        // Setup a subscriber to handle event messages as they arrive
        Flow.Subscriber<Value> valueSubscriber = new Flow.Subscriber<>() {
        Flow.Subscription subscription = null;

        // Setup an array list to capture all sent events
        ArrayList<Value> captured = new ArrayList<>();

            // Setup subscription callbacks
            @Override
            public void onSubscribe(Flow.Subscription s) {
              subscription = s;
              subscription.request(1);
            }

            @Override
            public void onNext(Value v) {
                System.out.println(v);
                captured.add(v);
                subscription.request(1);
            }

            @Override
            public void onError(Throwable throwable) {
                capturedEvents.completeExceptionally(throwable);
            }

            @Override
            public void onComplete() {
                capturedEvents.completeExceptionally(new IllegalStateException("not expecting the stream to complete"));
            }

        };

        // subscribe to publisher
        valuePublisher.subscribe(valueSubscriber);

        // blocking
        List<Value> events = capturedEvents.get();
    }
}

Before you run the example:

  1. Set the FAUNADB_SECRET environment variable, and optionally the FAUNADB_ENDPOINT environment variable (if you are using Region Groups or Fauna Dev).

  2. The collection Scores must exist.

Once the example is running, you can use the Fauna Dashboard, or another client application, to create or update the target document and watch the events arrive as the changes are made.

For example, if the document does not yet exist, you could run this query in the Fauna Dashboard Shell:

Create(Ref(Collection("Scores"), "1"), { data: { scores: [1, 2, 3] }})

Once the document exists, you could run this query:

Update(Ref(Collection("Scores"), "1"), { data: { scores: [5, 2, 3] }})

The streaming example waits indefinitely for events. Use Ctrl+C to terminate the program.

Scala

The following sections provide examples for managing streams with Flow or Monix, and assume that you have already created a FaunaClient.

Flow subscriber

It is possible to use the java.util.concurrent.Flow API directly by binding a Subscriber manually.

The following example subscribes to change events for a specific document:

package example

import faunadb._
import faunadb.query._
import faunadb.values._
import faunadb.FaunaClient._
import scala.concurrent._
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext._
import scala.concurrent.ExecutionContext.Implicits.global
import collection.JavaConverters._
import java.util.concurrent._
import java.util.concurrent.Flow._

object Example extends App {
  val secret = sys.env.getOrElse("FAUNADB_SECRET", {
    println("The FAUNADB_SECRET environment variable is not set, exiting.")
    sys.exit(1)
  })

  val endpoint = sys.env.getOrElse(
    "FAUNADB_ENDPOINT",
    "https://db.fauna.com/"
  )

  val client = FaunaClient(
    secret = secret,
    endpoint = endpoint
  )

  // Define a reference to the document that we want to stream
  // Note that the Scores collection must already exist
  val docRef = Ref(Collection("Scores"), "1")

  val result = client.stream(docRef).flatMap { publisher =>
    // Promise to hold the final state
    val capturedEventsP = Promise[List[Value]]

    // Our manual Subscriber
    val valueSubscriber = new Flow.Subscriber[Value] {
      var subscription: Flow.Subscription = null

      override def onSubscribe(s: Flow.Subscription): Unit = {
        subscription = s
        subscription.request(1)
      }

      override def onNext(v: Value): Unit = {
        println(v)
        subscription.request(1)
      }

      override def onError(t: Throwable): Unit =
        capturedEventsP.failure(t)

      override def onComplete(): Unit =
        capturedEventsP.failure(
          new IllegalStateException("not expecting the stream to complete")
        )
    }
    // subscribe to publisher
    publisher.subscribe(valueSubscriber)
    // wait for Future completion
    capturedEventsP.future
  }
  Await.result(result, Duration.Inf)
}

Before you run the example:

  1. Set the FAUNADB_SECRET environment variable, and optionally the FAUNADB_ENDPOINT environment variable (if you are using Region Groups or Fauna Dev).

  2. The collection Scores must exist.

Once the example is running, you can use the Fauna Dashboard, or another client application, to create or update the target document and watch the events arrive as the changes are made.

For example, if the document does not yet exist, you could run this query in the Fauna Dashboard Shell:

Create(Ref(Collection("Scores"), "1"), { data: { scores: [1, 2, 3] }})

Once the document exists, you could run this query:

Update(Ref(Collection("Scores"), "1"), { data: { scores: [5, 2, 3] }})

The streaming example waits indefinitely for events. Use Ctrl+C to terminate the program.

Monix

The reactive-streams standard offers a strong interoperability in the streaming ecosystem.

We can replicate the previous example using the Monix streaming library:

import faunadb._
import faunadb.query._
import monix.execution.Scheduler
import monix.reactive.Observable
import org.reactivestreams.{FlowAdapters, Publisher}

// docRef is a reference to the document for which we want to stream
// updates.  You can acquire a document reference with a query like the
// following, but it needs to work with the documents that you have.
// val docRef = Ref(Collection("scoreboards"), "123")

client.stream(docRef).flatMap { publisher =>
  val reactiveStreamsPublisher: Publisher[Value] = FlowAdapters.toPublisher(
    publisherValue
  )
  Observable.fromReactivePublisher(reactiveStreamsPublisher)
    .take(4) // 4 events
    .toListL
    .runToFuture(Scheduler.Implicits.global)
}

Set streaming

Java

The streaming API is built using the java.util.concurrent.Flow API, which enables users to establish flow-controlled components in which Publishers produce items consumed by one or more Subscribers, each managed by a Subscription.

The following example subscribes to change events for a set:

package example;

import static com.faunadb.client.query.Language.*;
import static com.faunadb.client.query.Language.TimeUnit.*;
import static com.faunadb.client.streaming.EventFields.*;
import static com.faunadb.client.types.Codec.*;
import static com.faunadb.client.types.Value.NullV.NULL;
import com.faunadb.client.query.Expr;
import com.faunadb.client.streaming.EventField;

import com.faunadb.client.FaunaClient;
import com.faunadb.client.types.*;
import com.faunadb.client.types.Value.*;

import java.util.Map;
import java.util.*;
import static java.util.Arrays.asList;

import java.util.concurrent.Flow;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * The class for our example app code.
 */
public class App {

    /**
     * The example.
     */
    public static void main(final String[] args) throws Exception {
        String secret   = System.getenv("FAUNADB_SECRET");
        String endpoint = System.getenv("FAUNADB_ENDPOINT");

        if (secret == null || secret == "") {
            System.out.println(
                "The FAUNADB_SECRET environment variable is not set, exiting."
            );
            System.exit(1);
        }
        if (endpoint == null || endpoint == "") {
            endpoint = "https://db.fauna.com/";
        }

        FaunaClient client = FaunaClient.builder()
            .withSecret(secret)
            .withEndpoint(endpoint)
            .build();

        // Define the reference to the target set
        Expr setRef = Documents(Collection("Scores"));

        // Define the stream fields to include
        List<EventField> fields = List.of(
            ActionField, DocumentField, IndexField
        );

        // Setup a Flow publisher to process the stream
        // `false` skips the target document snapshot event, which
        // is not required for set streaming.
        Flow.Publisher<Value> valuePublisher = client.stream(
            setRef, fields, false
        ).get();

        // Setup a future so that we can manage the stream's future
        // events
        CompletableFuture<List<Value>> capturedEvents = new CompletableFuture<>();

        // Setup a subscriber to handle event messages as they arrive
        Flow.Subscriber<Value> valueSubscriber = new Flow.Subscriber<>() {
        Flow.Subscription subscription = null;

        // Setup an array list to handle one or more events
        ArrayList<Value> captured = new ArrayList<>();

            // Setup subscription callbacks
            @Override
            public void onSubscribe(Flow.Subscription s) {
              subscription = s;
              subscription.request(1);
              System.out.println("onSubscribe");
            }

            @Override
            public void onNext(Value v) {
                System.out.println("onNext");
                System.out.println(v);
                captured.add(v);
                subscription.request(1);
            }

            @Override
            public void onError(Throwable throwable) {
                capturedEvents.completeExceptionally(throwable);
            }

            @Override
            public void onComplete() {
                capturedEvents.completeExceptionally(new IllegalStateException("not expecting the stream to complete"));
            }

        };

        // subscribe to publisher
        valuePublisher.subscribe(valueSubscriber);

        // blocking
        List<Value> events = capturedEvents.get();
    }
}

Before you run the example:

  1. Set the FAUNADB_SECRET environment variable, and optionally the FAUNADB_ENDPOINT environment variable (if you are using Region Groups or Fauna Dev).

  2. The collection Scores must exist.

Once the example is running, you can use the Fauna Dashboard, or another client application, to add or delete documents in the "Scores" collection and watch the events arrive as the changes are made. For example, you could run this query in the Fauna Dashboard's Shell:

Create(Collection("Scores"), { data: { scores: [5, 6, 7] }})

The streaming example waits indefinitely for events. Use Ctrl+C to terminate the program.

Scala

The following example subscribes to change events for a set:

package example

import faunadb._
import faunadb.query._
import faunadb.values._
import faunadb.FaunaClient._
import scala.concurrent._
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext._
import scala.concurrent.ExecutionContext.Implicits.global
import collection.JavaConverters._
import java.util.concurrent._
import java.util.concurrent.Flow._

object Example extends App {
  val secret = sys.env.getOrElse("FAUNADB_SECRET", {
    println("The FAUNADB_SECRET environment variable is not set, exiting.")
    sys.exit(1)
  })

  val endpoint = sys.env.getOrElse(
    "FAUNADB_ENDPOINT",
    "https://db.fauna.com/"
  )

  val client = FaunaClient(
    secret = secret,
    endpoint = endpoint
  )

  // Define a reference to the target set
  val setRef = Documents(Collection("Scores"))

  // Define the stream fields to include
  val streamOptions = List(ActionField, DocumentField, IndexField)

  val result = client.stream(setRef, streamOptions).flatMap { publisher =>
    // Promise to hold the final state
    val capturedEventsP = Promise[List[Value]]

    // Our manual Subscriber
    val valueSubscriber = new Flow.Subscriber[Value] {
      var subscription: Flow.Subscription = null

      override def onSubscribe(s: Flow.Subscription): Unit = {
        subscription = s
        subscription.request(1)
      }

      override def onNext(v: Value): Unit = {
        println(v)
        subscription.request(1)
      }

      override def onError(t: Throwable): Unit =
        capturedEventsP.failure(t)

      override def onComplete(): Unit =
        capturedEventsP.failure(
          new IllegalStateException("not expecting the stream to complete")
        )
    }
    // subscribe to publisher
    publisher.subscribe(valueSubscriber)
    // wait for Future completion
    capturedEventsP.future
  }
  Await.result(result, Duration.Inf)
}

Before you run the example:

  1. Set the FAUNADB_SECRET environment variable, and optionally the FAUNADB_ENDPOINT environment variable (if you are using Region Groups or Fauna Dev).

  2. The collection Scores must exist.

Once the example is running, you can use the Fauna Dashboard, or another client application, to add or delete documents in the "Scores" collection and watch the events arrive as the changes are made. For example, you could run this query in the Fauna Dashboard's Shell:

Create(Collection("Scores"), { data: { scores: [5, 6, 7] }})

The streaming example waits indefinitely for events. Use Ctrl+C to terminate the program.

The changes required to use Monix are not repeated here.

Is this article helpful? 

Tell Fauna how the article can be improved:
Visit Fauna's forums or email docs@fauna.com

Thank you for your feedback!