Using Neo4j-OGM with Spring Boot

In my last post, I detailed how to use the official Neo4j drivers within a Spring Boot application. In this post, I will take it one step further by explaining how to you cam utilise the Neo4j-OGM to encapsulate your business logic into java objects that can be mapped directly to the graph.


Neo4j OGM is an Object-Graph Mapping Library built in Java that allows you map Plain-Old-Java-Objects to nodes in the Neo4j database.  While calling Cypher queries through the official drivers can work well for smaller projects or complex queries, this can become hard work when working with larger applications.

A set of simple annotations can turn any class into a Domain Entity that can be manipulated with Java and persisted through an OGM session.  The OGM uses Cypher under the hood to interact with Neo4j through either Bolt or HTTP(s) protocols or in embedded mode.

The main benefit to this approach is that all domain knowledge can be encapsulated into code.  As a fan of Domain Driven Design, the patterns of repositories and services fit nicely into this pattern.


To get started with Neo4j-OGM, you’ll need to include the Neo4j OGM Core and the appropriate driver dependency. In this case, I will be using the bolt driver.

dependencies {
// ...

Once you have the dependencies, you can start to create domain entities.

Creating Entities

In this post, we’ll look at creating a simple social graph.  The graph will consist of :Person nodes related to each other with :KNOWS relationships.  Each person will have a first name, last name.  Each :KNOWS relationship will have a createdAt property.


The OGM uses an ID property to identify nodes and relationships.  Because we will use this on all entities and relationships, we can create a base class that all other entities extend.

First, we create a new class annotated with the @NodeEntity annotation.
public class Entity {
// ...

Next, we define a property to hold the ID.  This takes two annotations, the @Id annotation identifies the field as the identifier and the @GeneratedValue is used with the @Id annotation to define a generation strategy.
private Long id;

public Long getId() {
return id;

There are two strategies for ID generation, a UUID strategy or Internal ID strategy which is uses by default.

Person NodeEntity

We can extend the Entity class to create a new Person class, again annotated as a @NodeEntity.
public class Person extends Entity {
private String firstName;

private String lastName;

@Relationship( type = "KNOWS" )
Set<Knows> knows;

Inside this class I have defined two properties; firstName and lastName and defined a set of relationships using the @Relationship annotation.

In order to interact with these objects using Java, we can define getters and setters which manipulate these properties.
public void setLastName(String lastName) {
this.lastName = lastName;

public String getLastName() {
return lastName;

Knows RelationshipEntity

In the Person class, I have defined the knows property as a Set of objects with a type of Knows.  This class will be annotated as a @RelationshipEntity.  This annotation takes a type property which will be used when the data is saved or read.
@RelationshipEntity( type = "KNOWS" )
public class Knows extends Entity {
// ...

As with the Person entity, we assign properties directly to the relationship.  RelationshipEntity classes take a couple of extra annotations.  The @StartNode and @EndNode annotations identify the entities that are used as the start and end nodes of the relationships.  In this case, the start and end nodes for the relationship will be instances of the Person class that we defined above.
Person source;

Person target;

At the time of writing, Neo4j doesn’t support dates as a valid data type.  Instead, we can choose to store the date as a string or the seconds since epoch as a Long.  In order to use dates in our application, we can use a Converter to handle the conversion when storing the data and again when retrieving the information from the database.  The OGM comes with the following converters:

  • @DateLong – Converts java.util.Date or java.time.Instant to Long.
  • @DateString – Converts java.util.Date or java.time.Instant to a String.  By default, this will be converted to ISO 8601 format (yyyy-MM-dd’T’HH:mm:ss.SSSXXX) but a date format string can also be provided with the annotation.
  • @EnumString – Converts an enum to String.
  • @NumberString – Converts any object that extends java.lang.Number to a String.  This method can be used to handle BigInteger or BigDecimal data types.

My personal preference is to store dates in Long format, so I’ll use the @DateLong annotation.
Date since; 

Now we can use native java dates, while the OGM takes care of the conversion when hydrating the object or persisting it back to the graph.
public void setSince(Date since) {
this.since = since;

public Date getSince() {
return since;

Accessing Data with Sessions

We interact with the model using Sessions.  The OGM comes with a Session Factory that we can use to open a new session within a request.  In order to use this inside a service, we can expose it as a @Bean.  The configuration of the session is slightly different to the configuration of the official driver, but we can use the same configuration from
public org.neo4j.ogm.config.Configuration getConfiguration() {
return new org.neo4j.ogm.config.Configuration.Builder()
.uri( getUri() )
.credentials( username, password )

In this snippet, we’re using the OGM’s Configuration Builder to create a configuration object and exposing this as a bean.  Then, we can create a method that will take this configuration and use it to create an instance of the SessionFactory.  We can pass through an optional set of package names which the session factory will scan for domain objects.
public SessionFactory getSessionFactory(Neo4jConfiguration config) {
return new SessionFactory( config.getConfiguration(), "co.adamcowley.neobeans" );

By default the SessionFactory will be treated as a singleton and instantiated only once across the application.

Injecting into Services

Ideally, when modifying data held within the graph, we will do in a single responsibility class.  In Domain Driven Design terms, these are known as Services and are defined as objects that perform standalone changes to the model while retaining no encapsulated state.   Spring comes with a @Service stereotype annotation which can be applied to these classes.  This will indicate to Spring that this should be treated as a component that can be autowired into the application.

At the moment, we don’t have any data in the graph.  So let’s create a service that will create a new Person and give it a @Service annotation.  As this has been declared as a Spring component, we can annotate the constructor function.  This will ensure that on instantiation, each parameter will be instantiated and injected into the class.  As we will be interacting with the graph, we can autowire the SessionFactory.
public class CreatePerson {
private final SessionFactory sessionFactory;

public CreatePerson(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;

Persisting Data

We can use sessions to encapsulate business logic, in this case we can ensure that a person is created with both a first name and last name.  Once our business logic has been satisfied, we need to persist the changes in the database.  Inside a session, we have the ability to either save or delete information.  By calling the save method on a session, the OGM will take care of saving the data, either creating or updating each record as appropriate.  Where we are interacting with multiple users and relationships in sequence, we can also set an arbitrary depth to save to with each relationship in chain counting as one step.
public Person create(String firstName, String lastName) {
Person output = new Person();

output.setFirstName( firstName );
output.setLastName( lastName );

Session session = sessionFactory.openSession(); output );

return output;

Here, I have instantiated a new Person object, used the setter functions to set their first and last names.  Then, after opening a new session, the object is created.

Similar to the SessionFactory, we can use the @Autowired annotation to inject an instance of this service into any component.  In this case, I am using a POST request within a REST Controller to accept a firstName and lastName in the post body and passed these through to the function before returning the newly created node.
public class PersonController {
private final CreatePerson service;

public PersonController(CreatePerson service) {
this.service = service;

@RequestMapping(value = "/people", method = RequestMethod.POST)
public Person postIndex( @RequestBody Map<String, Object> body ) {
String firstName = (String) body.get("firstName");
String lastName = (String) body.get("lastName");

Person output = service.create(firstName, lastName);

return output;

Reading Data

In the previous post I used the driver to execute a cypher query to return a paginated list of results.  The Session interface offers a number of ways to load data, either using a collection of ID’s or using Filter before sorting and limiting the results.

Loading a Collection

The .loadAll() method will allow you to load a paginated Collection of results. To filter results, the method will accept one or more Filter instances. To paginate the results, you can provide a Pagination instance with the page number (zero based) and a limit.
public Collection<Person> getIndex(
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int limit)
Session session = factory.openSession();

Pagination pagination = new Pagination( page-1, limit );

Collection<Person> people = session.loadAll(Person.class, pagination);

return people;

Here I have created a Pagination instance using the page and limit parameters retrieved from the request.  This is then passed to the session.loadAll method along with the class that the results should be bound to.

To find a single node in the graph, you can use the load method.
@RequestMapping(value = "/people/{id}", method = RequestMethod.GET)
public Person getIndex( @PathVariable("id") Long id ) {
Session session = factory.openSession();

return session.load( Person.class, id );


Using an O(R/G)M can be a great approach to software development. Not least because they allow you to enforce business rules within your code base but also promote good coding standards and improve maintainability. From a Graph point of view, this could also lower the barrier to entry for developers starting out with graph databases. Any Java developer can interact with the Graph using plain Java rather than learning yet another query language.

If you are using the OGM with Spring, it is well worth taking a look at the Spring Data Neo4j project which uses the same annotation-styled approach while also providing repository patterns for retrieving and persisting data.