Processing Results

The Neo4j Java Driver provides you with three APIs for consuming results:

  • Synchronous API

  • Async API

  • Reactive API

The Three APIs

The most common and straightforward method of consuming results is with the synchronous API.

When using session.run(), tx.run(), or one of the two transaction functions, the query will return a Result object that you can process incrementally and then return the results of that processing.

For the asynchronous and reactive APIs you need to use different entry-points and API methods and helpers like a reactive framework. In return you get more efficient resource usage in the database, middleware and client by using the non-synchronous APIs.

Learn how to interact with Neo4j from Java using the Neo4j Java DriverSynchronous API
java
try (var session = driver.session()) {

    var res = session.readTransaction(tx -> tx.run(
            "MATCH (p:Person) RETURN p.name AS name LIMIT 10").list());
    res.stream()
            .map(row -> row.get("name"))
            .forEach(System.out::println);
} catch (Exception e) {
    // There was a problem with the
    // database connection or the query
    e.printStackTrace();
}
Learn how to interact with Neo4j from Java using the Neo4j Java DriverAsync API
java
var session = driver.asyncSession();
session.readTransactionAsync(tx -> tx.runAsync(
                "MATCH (p:Person) RETURN p.name AS name LIMIT 10")

        .thenApplyAsync(res -> res.listAsync(row -> row.get("name")))
        .thenAcceptAsync(System.out::println)
        .exceptionallyAsync(e -> {
            e.printStackTrace();
            return null;
        })
);
Learn how to interact with Neo4j from Java using the Neo4j Java DriverReactive API
java
Flux.usingWhen(Mono.fromSupplier(driver::rxSession),
    session -> session.readTransaction(tx -> {
        var rxResult = tx.run(
                "MATCH (p:Person) RETURN p.name AS name LIMIT 10");
        return Flux
            .from(rxResult.records())
            .map(r -> r.get("name").asString())
            .doOnNext(System.out::println)
            .then(Mono.from(rxResult.consume()));
    }
    ), RxSession::close);

The Result

The Result object, contains the records received by the Driver along with a set of additional meta data about the query execution and results.

An individual row of results is referred to as a Record, and can be accessed from the result various ways, as Iterator<Record> and via the stream(), the list() or single() methods.

A Record refers to the keyed set of values specified in the RETURN portion of the statement.

If no RETURN values are specified, the query will not return any results, and record results will be empty or throw an error in the case of single().

Additional meta data about the result and query is accessible from Result too (see below).

Records

You can access the records returned by the query through several means. A Result is an Iterator<Record> there are stream() and list() accessors for streaming and materialization/conversion.

java
Iterating over Records
// Get single row, error when more/less than 1
Record row = res.single();
// Materialize list
List<Record> rows = res.list();
// Stream results
Stream<Record> rowStream = res.stream();

// iterate (sorry no foreach)
while (res.hasNext()) {
    var next = res.next();
}

Key or Index

You can either access a value within the record by using the alias as specified in the RETURN portion of the Cypher statement or by specifying the column index (not recommended). The available keys can be accessed through res.keys().
java
Accessing record column values
// column names
row.keys();
// check for existence
row.containsKey("movie");
// number of columns
row.size();
// get a numeric value (int, long, float, double also possible)
Number count = row.get("movieCount").asInt(0);
// get a boolean value
boolean isDirector = row.get("isDirector").asBoolean();
// get node
row.get("movie").asNode();

Result Summary

The meta data ResultSummary accessed from Result.consume() include

  • statistics on how many nodes and relationships were created, updated, or deleted as a result of the query,

  • the query type

  • database and server info

  • query plan with and without profile

  • notifications

You can find more detail in the API docs for ResultSummary

For example, to get information about how long the query took to complete, you can use the following property:

java
Using the Result Summary
ResultSummary summary = res.consume();
// Time in milliseconds before receiving the first result
summary.resultAvailableAfter(TimeUnit.MILLISECONDS); // 10
// Time in milliseconds once the final result was consumed
summary.resultConsumedAfter(TimeUnit.MILLISECONDS); // 30

Another interesting part of the summary is the SummaryCounters available via counters(), which has update counts about a write-statement’s execution. You can check via counters.containsUpdates() if there were any updates.

java
Result Counters
SummaryCounters counters = summary.counters();
// some example counters
// nodes and relationships
counters.containsUpdates();
counters.nodesCreated();
counters.labelsAdded();
counters.relationshipsDeleted();
counters.propertiesSet();

// indexes and constraints
counters.indexesAdded();
counters.constraintsRemoved();
// updates to system db
counters.containsSystemUpdates();
counters.systemUpdates();

Check Your Understanding

1. What is the drawback of using the synchronous API to consume results?

  • ❏ The synchronous API is only available to Enterprise customers.

  • ✓ Results are only available once the Driver has received the final record.

  • ❏ You can only use the synchronous API within a Read Transactions.

Hint

If you are not subscribing to the record stream, you will only be able to access the first record once the entire stream has finished.

Solution

Results are only available once the Driver has received the final record. This can provide a negative experience for users waiting for the results of a long running query.

Lesson Summary

You now have all the information required to send Cypher queries to Neo4j and consume the results.

Next, we will look at the Cypher Type System and some of the considerations that you need to make when working with values coming from Neo4j in your Java application.