Using the Neo4j Driver in Spring Boot

By choice, I’d normally steer clear of Java if I wanted to write a lightweight application, preferring to write something with express. But since joining Neo4j last summer, I’ve become exposed to Java on a daily basis, writing User Defined Functions & Procedures in Neo4j.

However, since being introduced to Spring, I’ve been impressed by how quickly you can get a REST API up and running.  Starting with Spring Intializr, and after a few @Annotations, you can have a production grade application with security up in minutes.  Spring also has an ecosystem of projects around it including Spring Data, a consistent approach to CRUD operations.

Dependency Injection

One of the features of the Spring Framework that I find most useful is Dependency Injection, the ability to inject decoupled implementations of an Interface without the application worrying about the specifics.  In Spring terms, these are called Beans.  Any Plain-Old-Java-Object can be used as a Spring Bean and “wired” into a class or service.

This is the simplest way that we can interact with Neo4j in a Spring application.

Creating a Neo4j Driver @Bean

To create a Spring Boot application with Neo4j, first we’ll need to add the neo4j-java-driver and spring boot dependencies.

build.gradle
dependencies {
compile('org.neo4j.driver:neo4j-java-driver:1.5.1')

compile('org.springframework.boot:spring-boot-starter-web')
testCompile('org.springframework.boot:spring-boot-starter-test')
}

Once the dependencies are there, all we need to do is create a method that returns an instance of org.neo4j.driver.v1.Driver in the main application class and annotate it with the @Bean annotation.

NeobeansApplication.java
@SpringBootApplication
public class NeobeansApplication {

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}

@Bean
public Driver neo4jDriver() {
return GraphDatabase.driver("bolt://localhost:7687", AuthTokens.basic( "neo4j", "p455w0rd" ));
}

}

In this example, I am using a Basic auth token but Neo4j also supports Kerberos tokens and custom authentication schemes.

Once the bean has been defined, we can “wire” this into any class, controller or component using the @Autowired annotation.

PersonController.js
@RestController
public class PersonController {

@Autowired
Driver driver;

@RequestMapping("/people")
public List<Map<String, Object>> getIndex(
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int limit
) {
try ( Session session = driver.session() ) {
Number skip = (page - 1) * limit;

String query = "MATCH (p:Person) RETURN p ORDER BY p.name SKIP {skip} LIMIT {limit}";
Map<String, Object> params = new HashMap<String, Object>() {{
put("limit", limit);
put("skip", skip);
}};

return session.readTransaction(tx -> {
return tx.run(query, params)
.list( row -> row.get("p").asMap() );
});
}
}
}

The @RestController annotation will identify this class as a REST controller.

As you can see, I have used the @Autowired annotation to inject an instance of the driver into the class. This is then used to create a session, run a read query and return a paginated list of results.

That’s all there is to it…

Adding Dynamic Configuration

In the example above, the Neo4j credentials are hardcoded into the application. This would never be a good idea for production grade code.

We can use a @Configuration annotated class to pull properties from a configuration file. First off, we’ll need to configure gradle to read the values from src/main/resources/application.properties.

build.gradle
processResources {
expand(project.properties)
}

We can then add the properties that we would like to load into the application to the application.properties file.

application.properties
neo4j.scheme=bolt
neo4j.host=localhost
neo4j.port=7687

neo4j.auth.type=basic
neo4j.auth.username=neo4j
neo4j.auth.password=p455w0rd

To avoid clashes, I have prefixed each setting with neo4j.. For forward compatibility, I have also split out the auth details – this way we can build support for custom schemes and kerberos authentication into the application.

Next, we can create a @Configuration annotated class.

Neo4jConfiguration.java
@Configuration
public class Neo4jConfiguration {
// ...
}

In order to pull the properties into the application, we can annotate properties with @Value. The annotation will take the full key of the property, prefixed with a $ and wrapped in braces.

@Value("${neo4j.scheme:bolt}")
private String scheme;

In this example, I’ve provided a default value of bolt. If you do not provide a default value and the setting, an exception will be thrown on start up.

Next, we can use the value in neo4j.auth.type to create the appropriate token.

/**
* Use the neo4j.auth.type property to create an appropriate Auth Token
*
* @return AuthToken
*/
public AuthToken getAuthToken() {
switch ( authType ) {
case "basic":
return AuthTokens.basic(username, password);
case "kerberos":
return AuthTokens.kerberos(ticket);
default:
return AuthTokens.none();
}
}

Then combine the scheme, host, port and routing policy to create a valid URI.


/**
* Get the URI for the Neo4j Server
* @return String
*/
public String getUri() {
// Get the base URI (ie bolt://localhost:7474)
String uri = String.format("%s://%s:%s", scheme, host, port);

// If there is a CC routing policy, append it to the end of the string
if ( scheme == "bolt+routing" && routingPolicy != null ) {
uri += "?policy="+ routingPolicy;
}

return uri;
}

Finally, we can move our driver @Bean into the configuration file and use the methods to create a URI and Auth Token.

/**
* Create a new Driver instance
*
* @return Driver
*/
@Bean
public Driver neo4jDriver() {
String uri = getUri();
AuthToken token = getAuthToken();

return GraphDatabase.driver(uri, token);
}

Although the Bean definition has been moved, Spring is smart enough to pick up this change so we don’t have to modify the rest controller.

Conclusion

Hopefully this post has given you an introduction to interacting with Neo4j inside a Spring application. Spring has a vast ecosystem and this post only scratches the surface. For more information on Spring or the frameworks, head over to spring.io. Spring Data is an interesting project that is well worth reading up on, and even features an extension that allows you to map plain old Java objects to Nodes in the graph using Spring Data Neo4j.

You’ll hear more on Spring Data Neo4j from me when Spring 2.0 is released.

For now, the code to go with this blog post is available on Github, feel free to clone, fork or create a pull request if you spot anything untoward.