Chapter 4. Using Neo4j embedded in Java applications

It’s easy to use Neo4j embedded in Java applications. In this chapter you will find everything needed — from setting up the environment to doing something useful with your data.

When running your own code and Neo4j in the same JVM, there are a few things you should keep in mind:

  • Don’t create or retain more objects than you strictly need to. Large caches in particular tend to promote more objects to the old generation, thus increasing the need for expensive full garbage collections.
  • Don’t use internal Neo4j APIs. They are internal to Neo4j and subject to change without notice, which may break or change the behavior of your code.
  • Don’t enable the -XX:+TrustFinalNonStaticFields JVM flag when running in embedded mode.

4.1. Include Neo4j in your project

After selecting the appropriate edition for your platform, embed Neo4j in your Java application by including the Neo4j library jars in your build. The following sections will show how to do this by either altering the build path directly or by using dependency management.

4.1.1. Add Neo4j to the build path

Get the Neo4j libraries from one of these sources:

Add the jar files to your project:

JDK tools
Append to -classpath
Eclipse
  • Right-click on the project and then go Build Path → Configure Build Path. In the dialog, choose Add External JARs, browse to the Neo4j lib/ directory and select all of the jar files.
  • Another option is to use User Libraries.
IntelliJ IDEA
See Libraries, Global Libraries, and the Configure Library dialog
NetBeans
  • Right-click on the Libraries node of the project, choose Add JAR/Folder, browse to the Neo4j lib/ directory and select all of the jar files.
  • You can also handle libraries from the project node, see Managing a Project’s Classpath.

4.1.2. Editions

The following table outlines the available editions and their names for use with dependency management tools.

Follow the links in the table for details on dependency configuration with Apache Maven, Apache Buildr, Apache Ivy, Groovy Grape, Grails, Scala SBT!

Table 4.1. Neo4j editions
Edition Dependency Description

Community

org.neo4j:neo4j

a high performance, fully ACID transactional graph database

Enterprise

org.neo4j:neo4j-enterprise

adding advanced monitoring, online backup and High Availability clustering

The listed dependencies do not contain the implementation, but pulls it in transitively.

For information regarding licensing, see the Licensing Guide.

Javadocs can be downloaded packaged in jar files from Maven Central or read at javadocs.

4.1.3. Add Neo4j as a dependency

You can either go with the top-level artifact from the table above or include the individual components directly. The examples included here use the top-level artifact approach.

4.1.3.1. Maven

Add the dependency to your project along the lines of the snippet below. This is usually done in the pom.xml file found in the root directory of the project.

Maven dependency. 

<project>
...
 <dependencies>
  <dependency>
   <groupId>org.neo4j</groupId>
   <artifactId>neo4j</artifactId>
   <version>3.5.1</version>
  </dependency>
  ...
 </dependencies>
...
</project>

Where the artifactId is found in the editions table.

4.1.3.2. Eclipse and Maven

For development in Eclipse, it is recommended to install the m2e plugin and let Maven manage the project build classpath instead, see above. This also adds the possibility to build your project both via the command line with Maven and have a working Eclipse setup for development.

4.1.3.3. Ivy

Make sure to resolve dependencies from Maven Central, for example using this configuration in your ivysettings.xml file:

<ivysettings>
  <settings defaultResolver="main"/>
  <resolvers>
    <chain name="main">
      <filesystem name="local">
        <artifact pattern="${ivy.settings.dir}/repository/[artifact]-[revision].[ext]" />
      </filesystem>
      <ibiblio name="maven_central" root="http://repo1.maven.org/maven2/" m2compatible="true"/>
    </chain>
  </resolvers>
</ivysettings>

With that in place you can add Neo4j to the mix by having something along these lines to your 'ivy.xml' file:

..
<dependencies>
  ..
  <dependency org="org.neo4j" name="neo4j" rev="3.5.1"/>
  ..
</dependencies>
..

Where the name is found in the editions table above

4.1.3.4. Gradle

The example below shows an example gradle build script for including the Neo4j libraries.

def neo4jVersion = "3.5.1"
apply plugin: 'java'
repositories {
   mavenCentral()
}
dependencies {
   compile "org.neo4j:neo4j:${neo4jVersion}"
}

Where the coordinates (org.neo4j:neo4j in the example) are found in the editions table above.

4.1.4. Starting and stopping

To create a new database or open an existing one you instantiate a GraphDatabaseService.

        graphDb = new GraphDatabaseFactory().newEmbeddedDatabase( databaseDirectory );
        registerShutdownHook( graphDb );

If you are using the Enterprise Edition of Neo4j in embedded mode, you have to create your database with the EnterpriseGraphDatabaseFactory to enable the Enterprise Edition features.

If you wish to use the High Availability clustering features, instead use the HighlyAvailableGraphDatabaseFactory. This includes Enterprise Edition. HighlyAvailableGraphDatabaseFactory is deprecated and will be replaced in a future release.

There is no factory for the Causal Clustering features, as it is currently not possible to run a Causal Cluster in embedded mode.

The GraphDatabaseService instance can be shared among multiple threads. Note however that you can’t create multiple instances pointing to the same database.

To stop the database, call the shutdown() method:

        graphDb.shutdown();

To make sure Neo4j is shut down properly you can add a shutdown hook:

    private static void registerShutdownHook( final GraphDatabaseService graphDb )
    {
        // Registers a shutdown hook for the Neo4j instance so that it
        // shuts down nicely when the VM exits (even if you "Ctrl-C" the
        // running application).
        Runtime.getRuntime().addShutdownHook( new Thread()
        {
            @Override
            public void run()
            {
                graphDb.shutdown();
            }
        } );
    }

4.1.4.1. Starting an embedded database with configuration settings

To start Neo4j with configuration settings, a Neo4j properties file can be loaded like this:

        GraphDatabaseService graphDb = new GraphDatabaseFactory()
            .newEmbeddedDatabaseBuilder( testDirectory.databaseDir() )
            .loadPropertiesFromFile( pathToConfig + "neo4j.conf" )
            .newGraphDatabase();

Configuration settings can also be applied programmatically, like so:

        GraphDatabaseService graphDb = new GraphDatabaseFactory()
            .newEmbeddedDatabaseBuilder( testDirectory.databaseDir() )
            .setConfig( GraphDatabaseSettings.pagecache_memory, "512M" )
            .setConfig( GraphDatabaseSettings.string_block_size, "60" )
            .setConfig( GraphDatabaseSettings.array_block_size, "300" )
            .newGraphDatabase();

4.1.4.2. Starting an embedded read-only instance

If you want a read-only view of the database, create an instance this way:

        graphDb = new GraphDatabaseFactory().newEmbeddedDatabaseBuilder( dir )
                .setConfig( GraphDatabaseSettings.read_only, "true" )
                .newGraphDatabase();

Obviously the database has to already exist in this case.

Concurrent access to the same database files by multiple (read-only or write) instances is not supported.

4.2. Hello world

Learn how to create and access nodes and relationships. For information on project setup, see Section 4.1, “Include Neo4j in your project”.

Remember that a Neo4j graph consists of:

  • Nodes that are connected by
  • Relationships, with
  • Properties on both nodes and relationships.

All relationships have a type. For example, if the graph represents a social network, a relationship type could be KNOWS. If a relationship of the type KNOWS connects two nodes, that probably represents two people that know each other. A lot of the semantics (that is the meaning) of a graph is encoded in the relationship types of the application. And although relationships are directed they are equally well traversed regardless of which direction they are traversed.

The source code of this example is found here: EmbeddedNeo4j.java

4.2.1. Prepare the database

Relationship types can be created by using an enum. In this example we only need a single relationship type. This is how to define it:

    private enum RelTypes implements RelationshipType
    {
        KNOWS
    }

We also prepare some variables to use:

    GraphDatabaseService graphDb;
    Node firstNode;
    Node secondNode;
    Relationship relationship;

The next step is to start the database server. Note that if the directory given for the database doesn’t already exist, it will be created.

        graphDb = new GraphDatabaseFactory().newEmbeddedDatabase( databaseDirectory );
        registerShutdownHook( graphDb );

Note that starting a database server is an expensive operation, so don’t start up a new instance every time you need to interact with the database! The instance can be shared by multiple threads. Transactions are thread confined.

As seen, we register a shutdown hook that will make sure the database shuts down when the JVM exits. Now it’s time to interact with the database.

4.2.2. Wrap operations in a transaction

All operations have to be performed in a transaction. This is a conscious design decision, since we believe transaction demarcation to be an important part of working with a real enterprise database. Now, transaction handling in Neo4j is very easy:

        try ( Transaction tx = graphDb.beginTx() )
        {
            // Database operations go here
            tx.success();
        }

For more information on transactions, see Chapter 7, Transaction management and Java API for Transaction.

For brevity, we do not spell out wrapping of operations in a transaction throughout the manual.

4.2.3. Create a small graph

Now, let’s create a few nodes. The API is very intuitive. Feel free to have a look at the Neo4j Javadocs. They’re included in the distribution, as well. Here’s how to create a small graph consisting of two nodes, connected with one relationship and some properties:

            firstNode = graphDb.createNode();
            firstNode.setProperty( "message", "Hello, " );
            secondNode = graphDb.createNode();
            secondNode.setProperty( "message", "World!" );

            relationship = firstNode.createRelationshipTo( secondNode, RelTypes.KNOWS );
            relationship.setProperty( "message", "brave Neo4j " );

We now have a graph that looks like this:

Figure 4.1. Hello World Graph
alt

4.2.4. Print the result

After we’ve created our graph, let’s read from it and print the result.

            System.out.print( firstNode.getProperty( "message" ) );
            System.out.print( relationship.getProperty( "message" ) );
            System.out.print( secondNode.getProperty( "message" ) );

Which will output:

Hello, brave Neo4j World!

4.2.5. Remove the data

In this case we’ll remove the data before committing:

            // let's remove the data
            firstNode.getSingleRelationship( RelTypes.KNOWS, Direction.OUTGOING ).delete();
            firstNode.delete();
            secondNode.delete();

Note that deleting a node which still has relationships when the transaction commits will fail. This is to make sure relationships always have a start node and an end node.

4.2.6. Shut down the database server

Finally, shut down the database server when the application finishes:

        graphDb.shutdown();

4.3. Property values

Both nodes and relationships can have properties.

Properties are named values where the name is a string. Property values can be either a primitive or an array of one primitive type. For example String, int and int[] values are valid for properties.

NULL is not a valid property value.

Setting a property to NULL is equivalent to deleting the property.

Table 4.2. Property value types
Type Description

boolean

 

byte

8-bit integer

short

16-bit integer

int

32-bit integer

long

64-bit integer

float

32-bit IEEE 754 floating-point number

double

64-bit IEEE 754 floating-point number

char

16-bit unsigned integers representing Unicode characters

String

sequence of Unicode characters

org.neo4j.graphdb.spatial.Point

A 2D or 3D point object in a given coordinate system.

java.time.LocalDate

An instant capturing the date, but not the time, nor the timezone.

java.time.OffsetTime

An instant capturing the time of day, and the timezone offset, but not the date.

java.time.LocalTime

An instant capturing the time of day, but not the date, nor the timezone.

java.time.ZonedDateTime

An instant capturing the date, the time, and the timezone.

java.time.LocalDateTime

An instant capturing the date and the time, but not the timezone.

java.time.temporal.TemporalAmount

A temporal amount. This captures the difference in time between two instants.

For further details on float/double values, see Java Language Specification.

Take note that there are two cases where more than one Java type is mapped to a single Cypher type. When this happens, type information is lost. If these objects are returned from procedures, the original types cannot be recreated:

  • A Cypher Duration is created when either a java.time.Duration or a java.time.Period is provided. If a Duration is returned, only the common interface java.time.temporal.TemporalAmount will remain.
  • A Cypher DateTime is created when a java.time.OffsetDateTime is provided. If a DateTime is returned, it will be converted into a java.time.ZonedDateTime.

Strings that contain special characters can have inconsistent or non-deterministic ordering in Neo4j. For details, see Cypher Manual → Sorting of special characters.

4.4. User database with indexes

You have a user database, and want to retrieve users by name using indexes.

The source code used in this example is found here: EmbeddedNeo4jWithNewIndexing.java

To begin with, we start the database server:

import::{import-neo4j-examples-sources}/org/neo4j/examples/EmbeddedNeo4jWithNewIndexing.java[tag=startDb]

Then we have to configure the database to index users by name. This only needs to be done once.

Schema changes and data changes are not allowed in the same transaction. Each transaction must either change the schema or the data, but not both.

import::{import-neo4j-examples-sources}/org/neo4j/examples/EmbeddedNeo4jWithNewIndexing.java[tag=createIndex]

Indexes are populated asynchronously when they are first created. It is possible to use the core API to wait for index population to complete:

import::{import-neo4j-examples-sources}/org/neo4j/examples/EmbeddedNeo4jWithNewIndexing.java[tag=wait]

It is also possible to query the progress of the index population:

import::{import-neo4j-examples-sources}/org/neo4j/examples/EmbeddedNeo4jWithNewIndexing.java[tag=progress]

It’s time to add the users:

import::{import-neo4j-examples-sources}/org/neo4j/examples/EmbeddedNeo4jWithNewIndexing.java[tag=addUsers]

Please read Section 4.6, “Managing resources when using long running transactions” on how to properly close ResourceIterators returned from index lookups.

And here’s how to find a user by id:

import::{import-neo4j-examples-sources}/org/neo4j/examples/EmbeddedNeo4jWithNewIndexing.java[tag=findUsers]

When updating the name of a user, the index is updated as well:

import::{import-neo4j-examples-sources}/org/neo4j/examples/EmbeddedNeo4jWithNewIndexing.java[tag=updateUsers]

When deleting a user, it is automatically removed from the index:

import::{import-neo4j-examples-sources}/org/neo4j/examples/EmbeddedNeo4jWithNewIndexing.java[tag=deleteUsers]

In case we change our data model, we can drop the index as well:

import::{import-neo4j-examples-sources}/org/neo4j/examples/EmbeddedNeo4jWithNewIndexing.java[tag=dropIndex]

4.5. User database with explicit index

Unless you have specific reasons to use the explicit indexing, see Section 4.4, “User database with indexes” instead.

Please read Section 4.6, “Managing resources when using long running transactions” on how to properly close ResourceIterators returned from index lookups.

You have a user database, and want to retrieve users by name using the explicit indexing system.

The source code used in this example is found here: EmbeddedNeo4jWithIndexing.java

We have created two helper methods to handle user names and adding users to the database:

import::{import-neo4j-examples-sources}/org/neo4j/examples/EmbeddedNeo4jWithNewIndexing.java[tag=helperMethods]

The next step is to start the database server:

import::{import-neo4j-examples-sources}/org/neo4j/examples/EmbeddedNeo4jWithNewIndexing.java[tag=startDb]

It’s time to add the users:

import::{import-neo4j-examples-sources}/org/neo4j/examples/EmbeddedNeo4jWithNewIndexing.java[tag=addUsers]

And here’s how to find a user by Id:

import::{import-neo4j-examples-sources}/org/neo4j/examples/EmbeddedNeo4jWithNewIndexing.java[tag=findUsers]

4.6. Managing resources when using long running transactions

It is necessary to always open a transaction when accessing the database. Inside a long running transaction it is good practice to ensure that any ResourceIterators obtained inside the transaction are closed as early as possible. This is either achieved by just exhausting the iterator or by explicitly calling its close method.

What follows is an example of how to work with a ResourceIterator. As we don’t exhaust the iterator, we will close it explicitly using the close() method.

import::{import-neo4j-examples-sources}/org/neo4j/examples/EmbeddedNeo4jWithNewIndexing.java[tag=resourceIterator]

4.7. Controlling logging

To control logging in Neo4j embedded, use the Neo4j embedded logging framework.

Neo4j embedded provides logging via its own org.neo4j.logging.Log layer, and does not natively use any existing Java logging framework. All logging events produced by Neo4j have a name, a level and a message. The name is a FQCN (fully qualified class name).

Neo4j uses the following log levels:

ERROR

For serious errors that are almost always fatal

WARN

For events that are serious, but not fatal

INFO

Informational events

DEBUG

Debugging events

To enable logging, an implementation of org.neo4j.logging.LogProvider must be provided to the GraphDatabaseFactory, as follows:

import::{import-neo4j-examples-sources}/org/neo4j/examples/EmbeddedNeo4jWithCustomLogging.java[tag=startDbWithLogProvider]

Neo4j also includes a binding for SLF4J, which is available in the neo4j-slf4j library jar. This can be obtained via Maven:

<project>
...
 <dependencies>
  <dependency>
   <groupId>org.neo4j</groupId>
   <artifactId>neo4j-slf4j</artifactId>
   <version>3.5.1</version>
  </dependency>
  <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>${slf4j-version}</version>
  </dependency>
  ...
 </dependencies>
...
</project>

To use this binding, simply pass an instance of org.neo4j.logging.slf4j.Slf4jLogProvider to the GraphDatabaseFactory, as follows:

import::{import-neo4j-examples-sources}/org/neo4j/examples/EmbeddedNeo4jWithSLF4JLogging.java[tag=startDbWithSlf4jLogProvider]

All log output can then be controlled via SLF4J configuration.

4.8. Basic unit testing

The basic pattern of unit testing with Neo4j is illustrated by the following example.

To access the Neo4j testing facilities you should have the neo4j-kernel 'tests.jar' together with the neo4j-io 'tests.jar' on the classpath during tests. You can download them from Maven Central: org.neo4j:neo4j-kernel and org.neo4j:neo4j-io.

Using Maven as a dependency manager you would typically add this dependency together with JUnit and Hamcrest like so:

Maven dependency. 

<project>
...
 <dependencies>
  <dependency>
   <groupId>org.neo4j</groupId>
   <artifactId>neo4j-kernel</artifactId>
   <version>3.5.1</version>
   <type>test-jar</type>
   <scope>test</scope>
  </dependency>
  <dependency>
   <groupId>org.neo4j</groupId>
   <artifactId>neo4j-io</artifactId>
   <version>3.5.1</version>
   <type>test-jar</type>
   <scope>test</scope>
  </dependency>
  <dependency>
   <groupId>junit</groupId>
   <artifactId>junit</artifactId>
   <version>4.12</version>
   <scope>test</scope>
  </dependency>
  <dependency>
   <groupId>org.hamcrest</groupId>
   <artifactId>hamcrest-all</artifactId>
   <version>1.3</version>
   <scope>test</scope>
  </dependency>
  ...
 </dependencies>
...
</project>

Observe that the <type>test-jar</type> is crucial. Without it you would get the common neo4j-kernel jar, not the one containing the testing facilities.

With that in place, we’re ready to code our tests.

For the full source code of this example see: Neo4jBasicDocTest.java

Before each test, create a fresh database:

import::{import-neo4j-examples-test-sources}/org/neo4j/examples/Neo4jBasicDocTest.java[tag=beforeTest]

After the test has executed, the database should be shut down:

import::{import-neo4j-examples-test-sources}/org/neo4j/examples/Neo4jBasicDocTest.java[tag=afterTest]

During a test, create nodes and check to see that they are there, while enclosing write operations in a transaction.

import::{import-neo4j-examples-test-sources}/org/neo4j/examples/Neo4jBasicDocTest.java[tag=unitTest]

If you want to set configuration parameters at database creation, it’s done like this:

import::{import-neo4j-examples-test-sources}/org/neo4j/examples/Neo4jBasicDocTest.java[tag=startDbWithConfig]

4.9. Traversal

For reading about traversals, see Chapter 5, The traversal framework.

4.9.1. The Matrix

This is the first graph we want to traverse into:

Figure 4.2. Matrix node space view
examples matrix

The source code of this example is found here: NewMatrix.java

Friends and friends of friends. 

    private Traverser getFriends(
            final Node person )
    {
        TraversalDescription td = graphDb.traversalDescription()
                .breadthFirst()
                .relationships( RelTypes.KNOWS, Direction.OUTGOING )
                .evaluator( Evaluators.excludeStartPosition() );
        return td.traverse( person );
    }

Let’s perform the actual traversal and print the results:

            int numberOfFriends = 0;
            String output = neoNode.getProperty( "name" ) + "'s friends:\n";
            Traverser friendsTraverser = getFriends( neoNode );
            for ( Path friendPath : friendsTraverser )
            {
                output += "At depth " + friendPath.length() + " => "
                          + friendPath.endNode()
                                  .getProperty( "name" ) + "\n";
                numberOfFriends++;
            }
            output += "Number of friends found: " + numberOfFriends + "\n";

Which will give us the following output:

Thomas Anderson's friends:
At depth 1 => Morpheus
At depth 1 => Trinity
At depth 2 => Cypher
At depth 3 => Agent Smith
Number of friends found: 4

Who coded the Matrix? 

    private Traverser findHackers( final Node startNode )
    {
        TraversalDescription td = graphDb.traversalDescription()
                .breadthFirst()
                .relationships( RelTypes.CODED_BY, Direction.OUTGOING )
                .relationships( RelTypes.KNOWS, Direction.OUTGOING )
                .evaluator(
                        Evaluators.includeWhereLastRelationshipTypeIs( RelTypes.CODED_BY ) );
        return td.traverse( startNode );
    }

Print out the result:

            String output = "Hackers:\n";
            int numberOfHackers = 0;
            Traverser traverser = findHackers( getNeoNode() );
            for ( Path hackerPath : traverser )
            {
                output += "At depth " + hackerPath.length() + " => "
                          + hackerPath.endNode()
                                  .getProperty( "name" ) + "\n";
                numberOfHackers++;
            }
            output += "Number of hackers found: " + numberOfHackers + "\n";

Now we know who coded the Matrix:

Hackers:
At depth 4 => The Architect
Number of hackers found: 1

4.9.1.1. Walking an ordered path

This example shows how to use a path context holding a representation of a path.

The source code of this example is found here: OrderedPath.java

Create a toy graph. 

            Node A = db.createNode();
            Node B = db.createNode();
            Node C = db.createNode();
            Node D = db.createNode();

            A.createRelationshipTo( C, REL2 );
            C.createRelationshipTo( D, REL3 );
            A.createRelationshipTo( B, REL1 );
            B.createRelationshipTo( C, REL2 );

alt

Now, the order of relationships (REL1REL2REL3) is stored in an ArrayList. Upon traversal, the Evaluator can check against it to ensure that only paths are included and returned that have the predefined order of relationships:

Define how to walk the path. 

        final ArrayList<RelationshipType> orderedPathContext = new ArrayList<>();
        orderedPathContext.add( REL1 );
        orderedPathContext.add( withName( "REL2" ) );
        orderedPathContext.add( withName( "REL3" ) );
        TraversalDescription td = db.traversalDescription()
                .evaluator( new Evaluator()
                {
                    @Override
                    public Evaluation evaluate( final Path path )
                    {
                        if ( path.length() == 0 )
                        {
                            return Evaluation.EXCLUDE_AND_CONTINUE;
                        }
                        RelationshipType expectedType = orderedPathContext.get( path.length() - 1 );
                        boolean isExpectedType = path.lastRelationship()
                                .isType( expectedType );
                        boolean included = path.length() == orderedPathContext.size() && isExpectedType;
                        boolean continued = path.length() < orderedPathContext.size() && isExpectedType;
                        return Evaluation.of( included, continued );
                    }
                } )
                .uniqueness( Uniqueness.NODE_PATH );

Note that we set the uniqueness to Uniqueness.NODE_PATH as we want to be able to revisit the same node during the traversal, but not the same path.

Perform the traversal and print the result. 

            Traverser traverser = td.traverse( A );
            PathPrinter pathPrinter = new PathPrinter( "name" );
            for ( Path path : traverser )
            {
                output += Paths.pathToString( path, pathPrinter );
            }

Which will output:

(A)--[REL1]-->(B)--[REL2]-->(C)--[REL3]-->(D)

In this case we use a custom class to format the path output. This is how it’s done:

    static class PathPrinter implements Paths.PathDescriptor<Path>
    {
        private final String nodePropertyKey;

        public PathPrinter( String nodePropertyKey )
        {
            this.nodePropertyKey = nodePropertyKey;
        }

        @Override
        public String nodeRepresentation( Path path, Node node )
        {
            return "(" + node.getProperty( nodePropertyKey, "" ) + ")";
        }

        @Override
        public String relationshipRepresentation( Path path, Node from, Relationship relationship )
        {
            String prefix = "--", suffix = "--";
            if ( from.equals( relationship.getEndNode() ) )
            {
                prefix = "<--";
            }
            else
            {
                suffix = "-->";
            }
            return prefix + "[" + relationship.getType().name() + "]" + suffix;
        }
    }

4.9.2. Uniqueness of Paths in traversals

This example is demonstrating the use of node uniqueness. Below an imaginary domain graph with Principals that own pets that are descendant to other pets.

Figure 4.3. Descendants example graph
alt

In order to return all descendants of Pet0 which have the relation owns to Principal1 (Pet1 and Pet3), the Uniqueness of the traversal needs to be set to NODE_PATH rather than the default NODE_GLOBAL. This way nodes can be traversed more that once, and paths that have different nodes but can have some nodes in common (like the start and end node) can be returned.

        final Node target = data.get().get( "Principal1" );
        TraversalDescription td = db.traversalDescription()
                .uniqueness( Uniqueness.NODE_PATH )
                .evaluator( new Evaluator()
        {
            @Override
            public Evaluation evaluate( Path path )
            {
                boolean endNodeIsTarget = path.endNode().equals( target );
                return Evaluation.of( endNodeIsTarget, !endNodeIsTarget );
            }
        } );

        Traverser results = td.traverse( start );

This will return the following paths:

(2)-[descendant,2]->(0)<-[owns,5]-(1)
(2)-[descendant,0]->(5)<-[owns,3]-(1)

In the default path.toString() implementation, (1)--[knows,2]-→(4) denotes a node with ID=1 having a relationship with ID=2 or type knows to a node with ID=4.

Let’s create a new TraversalDescription from the old one, having NODE_GLOBAL uniqueness to see the difference.

The TraversalDescription object is immutable, so we have to use the new instance returned with the new uniqueness setting.

        TraversalDescription nodeGlobalTd = td.uniqueness( Uniqueness.NODE_GLOBAL );
        results = nodeGlobalTd.traverse( start );

Now only one path is returned:

(2)-[descendant,2]->(0)<-[owns,5]-(1)

4.9.3. Social network

The following example uses the new enhanced traversal API.

Social networks (know as social graphs out on the web) are natural to model with a graph. This example shows a very simple social model that connects friends and keeps track of status updates.

The source code of the example is found here: socnet

4.9.3.1. Simple social model

Figure 4.4. Social network data model
socnet model

The data model for a social network is pretty simple: Persons with names and StatusUpdates with timestamped text. These entities are then connected by specific relationships.

  • Person

    • friend: relates two distinct Person instances (no self-reference)
    • status: connects to the most recent StatusUpdate
  • StatusUpdate

    • next: points to the next StatusUpdate in the chain, which was posted before the current one

4.9.3.2. Status graph instance

The StatusUpdate list for a Person is a linked list. The head of the list (the most recent status) is found by following status. Each subsequent StatusUpdate is connected by next.

Here’s an example where Andrew micro-blogged his way to work in the morning:

alt

To read the status updates, we can create a traversal, like so:

        TraversalDescription traversal = graphDb().traversalDescription()
                .depthFirst()
                .relationships( NEXT );

This gives us a traverser that will start at one StatusUpdate, and will follow the chain of updates until they run out. Traversers are lazy loading, so it’s performant even when dealing with thousands of statuses — they are not loaded until we actually consume them.

4.9.3.3. Activity stream

Once we have friends, and they have status messages, we might want to read our friends status' messages, in reverse time order — latest first. To do this, we go through these steps:

  1. Gather all friend’s status update iterators in a list — latest date first.
  2. Sort the list.
  3. Return the first item in the list.
  4. If the first iterator is exhausted, remove it from the list. Otherwise, get the next item in that iterator.
  5. Go to step 2 until there are no iterators left in the list.

Animated, the sequence looks like this.

The code looks like:

        PositionedIterator<StatusUpdate> first = statuses.get(0);
        StatusUpdate returnVal = first.current();

        if ( !first.hasNext() )
        {
            statuses.remove( 0 );
        }
        else
        {
            first.next();
            sort();
        }

        return returnVal;

4.10. Domain entities

This page demonstrates one way to handle domain entities when using Neo4j. The principle at use is to wrap the entities around a node (the same approach can be used with relationships as well).

The source code of the examples is found here: Person.java

First off, store the node and make it accessible inside the package:

    private final Node underlyingNode;

    Person( Node personNode )
    {
        this.underlyingNode = personNode;
    }

    protected Node getUnderlyingNode()
    {
        return underlyingNode;
    }

Delegate attributes to the node:

    public String getName()
    {
        return (String)underlyingNode.getProperty( NAME );
    }

Make sure to override these methods:

    @Override
    public int hashCode()
    {
        return underlyingNode.hashCode();
    }

    @Override
    public boolean equals( Object o )
    {
        return o instanceof Person &&
                underlyingNode.equals( ( (Person)o ).getUnderlyingNode() );
    }

    @Override
    public String toString()
    {
        return "Person[" + getName() + "]";
    }

4.11. Graph algorithm examples

For details on the graph algorithm usage, see the Javadocs.

The source code used in the example is found here: PathFindingDocTest.java

Calculating the shortest path (least number of relationships) between two nodes:

        Node startNode = graphDb.createNode();
        Node middleNode1 = graphDb.createNode();
        Node middleNode2 = graphDb.createNode();
        Node middleNode3 = graphDb.createNode();
        Node endNode = graphDb.createNode();
        createRelationshipsBetween( startNode, middleNode1, endNode );
        createRelationshipsBetween( startNode, middleNode2, middleNode3, endNode );

        // Will find the shortest path between startNode and endNode via
        // "MY_TYPE" relationships (in OUTGOING direction), like f.ex:
        //
        // (startNode)-->(middleNode1)-->(endNode)
        //
        PathFinder<Path> finder = GraphAlgoFactory.shortestPath(
            PathExpanders.forTypeAndDirection( ExampleTypes.MY_TYPE, Direction.OUTGOING ), 15 );
        Iterable<Path> paths = finder.findAllPaths( startNode, endNode );

Using Dijkstra’s algorithm to calculate cheapest path between node A and B where each relationship can have a weight (i.e. cost) and the path(s) with least cost are found.

        PathFinder<WeightedPath> finder = GraphAlgoFactory.dijkstra(
            PathExpanders.forTypeAndDirection( ExampleTypes.MY_TYPE, Direction.BOTH ), "cost" );

        WeightedPath path = finder.findSinglePath( nodeA, nodeB );

        // Get the weight for the found path
        path.weight();

Using A* to calculate the cheapest path between node A and B, where cheapest is for example the path in a network of roads which has the shortest length between node A and B. Here’s our example graph:

A* algorithm example graph
        Node nodeA = createNode( "name", "A", "x", 0d, "y", 0d );
        Node nodeB = createNode( "name", "B", "x", 7d, "y", 0d );
        Node nodeC = createNode( "name", "C", "x", 2d, "y", 1d );
        Relationship relAB = createRelationship( nodeA, nodeC, "length", 2d );
        Relationship relBC = createRelationship( nodeC, nodeB, "length", 3d );
        Relationship relAC = createRelationship( nodeA, nodeB, "length", 10d );

        EstimateEvaluator<Double> estimateEvaluator = new EstimateEvaluator<Double>()
        {
            @Override
            public Double getCost( final Node node, final Node goal )
            {
                double dx = (Double) node.getProperty( "x" ) - (Double) goal.getProperty( "x" );
                double dy = (Double) node.getProperty( "y" ) - (Double) goal.getProperty( "y" );
                double result = Math.sqrt( Math.pow( dx, 2 ) + Math.pow( dy, 2 ) );
                return result;
            }
        };
        PathFinder<WeightedPath> astar = GraphAlgoFactory.aStar(
                PathExpanders.allTypesAndDirections(),
                CommonEvaluators.doubleCostEvaluator( "length" ), estimateEvaluator );
        WeightedPath path = astar.findSinglePath( nodeA, nodeB );

4.12. Reading a management attribute

The JmxUtils class includes methods to access Neo4j management beans. The common JMX service can be used as well, but from your code you probably rather want to use the approach outlined here.

The source code of the example is found here: JmxDocTest.java

This example shows how to get the start time of a database:

    private static Date getStartTimeFromManagementBean(
            GraphDatabaseService graphDbService )
    {
        ObjectName objectName = JmxUtils.getObjectName( graphDbService, "Kernel" );
        Date date = JmxUtils.getAttribute( objectName, "KernelStartTime" );
        return date;
    }

Depending on which Neo4j edition you are using different sets of management beans are available.

4.13. How to create unique nodes

This section is about how to ensure uniqueness of a property when creating nodes. For an overview of the topic, see Section 7.6, “Creating unique nodes”.

4.13.1. Get or create unique node using Cypher and unique constraints

Create a unique constraint. 

        try ( Transaction tx = graphdb.beginTx() )
        {
            graphdb.schema()
                    .constraintFor( Label.label( "User" ) )
                    .assertPropertyIsUnique( "name" )
                    .create();
            tx.success();
        }

Use MERGE to create a unique node. 

        Node result = null;
        ResourceIterator<Node> resultIterator = null;
        try ( Transaction tx = graphDb.beginTx() )
        {
            String queryString = "MERGE (n:User {name: $name}) RETURN n";
            Map<String, Object> parameters = new HashMap<>();
            parameters.put( "name", username );
            resultIterator = graphDb.execute( queryString, parameters ).columnAs( "n" );
            result = resultIterator.next();
            tx.success();
            return result;
        }

4.13.2. Get or create unique node using an explicit index

While this is a working solution, please consider using the preferred solution at Section 4.13.1, “Get or create unique node using Cypher and unique constraints” instead.

By using put-if-absent functionality, entity uniqueness can be guaranteed using an index.

Here the index acts as the lock and will only lock the smallest part needed to guarantee uniqueness across threads and transactions. To get the more high-level get-or-create functionality make use of UniqueFactory as seen in the example below.

Create a factory for unique nodes at application start. 

        try ( Transaction tx = graphDb.beginTx() )
        {
            UniqueFactory.UniqueNodeFactory result = new UniqueFactory.UniqueNodeFactory( graphDb, "users" )
            {
                @Override
                protected void initialize( Node created, Map<String, Object> properties )
                {
                    created.addLabel( Label.label( "User" ) );
                    created.setProperty( "name", properties.get( "name" ) );
                }
            };
            tx.success();
            return result;
        }

Use the unique node factory to get or create a node. 

        try ( Transaction tx = graphDb.beginTx() )
        {
            Node node = factory.getOrCreate( "name", username );
            tx.success();
            return node;
        }

4.13.3. Pessimistic locking for node creation

While this is a working solution, please consider using the preferred solution at Section 4.13.1, “Get or create unique node using Cypher and unique constraints” instead.

One might be tempted to use Java synchronization for pessimistic locking, but this is dangerous. By mixing locks in Neo4j and in the Java runtime, it is easy to produce deadlocks that are not detectable by Neo4j. As long as all locking is done by Neo4j, all deadlocks will be detected and avoided. Also, a solution using manual synchronization doesn’t ensure uniqueness in an HA environment.

This example uses a single "lock node" for locking. We create it only as a place to put locks, nothing else.

Create a lock node at application start. 

        try ( Transaction tx = graphDb.beginTx() )
        {
            final Node lockNode = graphDb.createNode();
            tx.success();
            return lockNode;
        }

Use the lock node to ensure nodes are not created concurrently. 

        try ( Transaction tx = graphDb.beginTx() )
        {
            Index<Node> usersIndex = graphDb.index().forNodes( "users" );
            Node userNode = usersIndex.get( "name", username ).getSingle();
            if ( userNode != null )
            {
                return userNode;
            }

            tx.acquireWriteLock( lockNode );
            userNode = usersIndex.get( "name", username ).getSingle();
            if ( userNode == null )
            {
                userNode = graphDb.createNode( Label.label( "User" ) );
                usersIndex.add( userNode, "name", username );
                userNode.setProperty( "name", username );
            }
            tx.success();
            return userNode;
        }

Note that finishing the transaction will release the lock on the lock node.

4.14. Accessing Neo4j embedded via the Bolt protocol

Open a Bolt connector to your embedded instance to get GUI administration and other benefits.

The Neo4j Browser and the official Neo4j Drivers use the Bolt database protocol to communicate with Neo4j. By default, Neo4j Embedded does not expose a Bolt connector, but you can enable one. Doing so allows you to connect the services Neo4j Browser to your embedded instance.

It also gives you a way to incrementally transfer an existing Embedded application to use Neo4j Drivers instead. Migrating to Neo4j Drivers means you become free to choose to run Neo4j Embedded or Neo4j Server, without changing your application code.

To add a Bolt Connector to your embedded database, you need to add the Bolt extension to your class path. This is done by adding an additional dependency to your project.

<project>
...
 <dependencies>

  <dependency>
    <groupId>org.neo4j</groupId>
    <artifactId>neo4j-bolt</artifactId>
    <version>3.5.1</version>
  </dependency>
  ...
 </dependencies>
...
</project>

With this dependency in place, you can configure Neo4j to enable a Bolt connector.

        GraphDatabaseSettings.BoltConnector bolt = GraphDatabaseSettings.boltConnector( "0" );

        GraphDatabaseService graphDb = new GraphDatabaseFactory()
                .newEmbeddedDatabaseBuilder( DB_PATH )
                .setConfig( bolt.type, "BOLT" )
                .setConfig( bolt.enabled, "true" )
                .setConfig( bolt.address, "localhost:7687" )
                .newGraphDatabase();

4.15. Terminating a running transaction

Sometimes you may want to terminate (abort) a long running transaction from another thread.

The source code used in this example is found here: TerminateTransactions.java

To begin with, we start the database server:

        GraphDatabaseService graphDb = new GraphDatabaseFactory().newEmbeddedDatabase( databaseDirectory );

Now we start creating an infinite binary tree of nodes in the database, as an example of a long running transaction.

        RelationshipType relType = RelationshipType.withName( "CHILD" );
        Queue<Node> nodes = new LinkedList<>();
        int depth = 1;

        try ( Transaction tx = graphDb.beginTx() )
        {
            Node rootNode = graphDb.createNode();
            nodes.add( rootNode );

            for (; true; depth++) {
                int nodesToExpand = nodes.size();
                for (int i = 0; i < nodesToExpand; ++i) {
                    Node parent = nodes.remove();

                    Node left = graphDb.createNode();
                    Node right = graphDb.createNode();

                    parent.createRelationshipTo( left, relType );
                    parent.createRelationshipTo( right, relType );

                    nodes.add( left );
                    nodes.add( right );
                }
            }
        }
        catch ( TransactionTerminatedException ignored )
        {
            return String.format( "Created tree up to depth %s in 1 sec", depth );
        }

After waiting for some time, we decide to terminate the transaction. This is done from a separate thread.

                    tx.terminate();

Running this will execute the long running transaction for about one second and prints the maximum depth of the tree that was created before the transaction was terminated. No changes are actually made to the data — because the transaction has been terminated, the end result is as if no operations were performed.

Example output. 

Created tree up to depth 17 in 1 sec

Finally, let’s shut down the database again.

            graphDb.shutdown();

4.16. Execute Cypher queries from Java

The full source code of the example: JavaQuery.java

In Java, you can use the Cypher query language as per the example below. First, let’s add some data.

        GraphDatabaseService db = new GraphDatabaseFactory().newEmbeddedDatabase( databaseDirectory );

        try ( Transaction tx = db.beginTx())
        {
            Node myNode = db.createNode();
            myNode.setProperty( "name", "my node" );
            tx.success();
        }

Execute a query:

        try ( Transaction ignored = db.beginTx();
              Result result = db.execute( "MATCH (n {name: 'my node'}) RETURN n, n.name" ) )
        {
            while ( result.hasNext() )
            {
                Map<String,Object> row = result.next();
                for ( Entry<String,Object> column : row.entrySet() )
                {
                    rows += column.getKey() + ": " + column.getValue() + "; ";
                }
                rows += "\n";
            }
        }

In the above example, we also show how to iterate over the rows of the Result.

The code will generate:

n.name: my node; n: Node[0];

When using an Result, you should consume the entire result (iterate over all rows using next(), iterating over the iterator from columnAs() or calling for example resultAsString()). Failing to do so will not properly clean up resources used by the Result object, leading to unwanted behavior, such as leaking transactions. In case you don’t want to iterate over all of the results, make sure to invoke close() as soon as you are done, to release the resources tied to the result.

Using a try-with-resources statement will make sure that the result is closed at the end of the statement. This is the recommended way to handle results.

You can also get a list of the columns in the result like this:

            List<String> columns = result.columns();

This gives us:

[n, n.name]

To fetch the result items from a single column, do like below. In this case we’ll have to read the property from the node and not from the result.

            Iterator<Node> n_column = result.columnAs( "n" );
            for ( Node node : Iterators.asIterable( n_column ) )
            {
                nodeResult = node + ": " + node.getProperty( "name" );
            }

In this case there’s only one node in the result:

Node[0]: my node

Only use this if the result only contains a single column, or you are only interested in a single column of the result.

resultAsString(), writeAsStringTo(), columnAs() cannot be called more than once on the same Result object, as they consume the result. In the same way, part of the result gets consumed for every call to next(). You should instead use only one and if you need the facilities of the other methods on the same query result instead create a new Result.

For more information on the Java interface to Cypher, see the Java API.

For more information and examples for Cypher, see Neo4j Cypher Manual.

4.17. Query parameters

For more information on parameters see the Neo4j Cypher Manual.

Below follows example of how to use parameters when executing Cypher queries from Java.

Node id. 

        Map<String, Object> params = new HashMap<>();
        params.put( "id", 0 );
        String query = "MATCH (n) WHERE id(n) = $id RETURN n.name";
        Result result = db.execute( query, params );

Node object. 

        Map<String, Object> params = new HashMap<>();
        params.put( "node", bobNode );
        String query = "MATCH (n:Person) WHERE n = $node RETURN n.name";
        Result result = db.execute( query, params );

Multiple node ids. 

        Map<String, Object> params = new HashMap<>();
        params.put( "ids", Arrays.asList( 0, 1, 2 ) );
        String query = "MATCH (n) WHERE id(n) IN $ids RETURN n.name";
        Result result = db.execute( query, params );

String literal. 

        Map<String, Object> params = new HashMap<>();
        params.put( "name", "Johan" );
        String query = "MATCH (n:Person) WHERE n.name = $name RETURN n";
        Result result = db.execute( query, params );

Index value. 

            Map<String, Object> params = new HashMap<>();
            params.put( "value", "Michaela" );
            String query = "START n=node:people(name = $value) RETURN n";
            Result result = db.execute( query, params );

Index query. 

            Map<String, Object> params = new HashMap<>();
            params.put( "query", "name:Bob" );
            String query = "START n=node:people($query) RETURN n";
            Result result = db.execute( query, params );

Numeric parameters for SKIP and LIMIT

        Map<String, Object> params = new HashMap<>();
        params.put( "s", 1 );
        params.put( "l", 1 );
        String query = "MATCH (n:Person) RETURN n.name SKIP $s LIMIT $l";
        Result result = db.execute( query, params );

Regular expression. 

        Map<String, Object> params = new HashMap<>();
        params.put( "regex", ".*h.*" );
        String query = "MATCH (n:Person) WHERE n.name =~ $regex RETURN n.name";
        Result result = db.execute( query, params );

Create node with properties. 

        Map<String, Object> props = new HashMap<>();
        props.put( "name", "Andy" );
        props.put( "position", "Developer" );

        Map<String, Object> params = new HashMap<>();
        params.put( "props", props );
        String query = "CREATE ($props)";
        db.execute( query, params );

Create multiple nodes with properties. 

        Map<String, Object> n1 = new HashMap<>();
        n1.put( "name", "Andy" );
        n1.put( "position", "Developer" );
        n1.put( "awesome", true );

        Map<String, Object> n2 = new HashMap<>();
        n2.put( "name", "Michael" );
        n2.put( "position", "Developer" );
        n2.put( "children", 3 );

        Map<String, Object> params = new HashMap<>();
        List<Map<String, Object>> maps = Arrays.asList( n1, n2 );
        params.put( "props", maps );
        String query = "UNWIND $props AS properties CREATE (n:Person) SET n = properties RETURN n";
        db.execute( query, params );

Setting all properties on node. 

            Map<String, Object> n1 = new HashMap<>();
            n1.put( "name", "Andy" );
            n1.put( "position", "Developer" );

            Map<String, Object> params = new HashMap<>();
            params.put( "props", n1 );

            String query = "MATCH (n:Person) WHERE n.name='Michaela' SET n = $props";
            db.execute( query, params );