Usage
Add the JDBC driver to your application, for example as a Gradle dependency:
dependencies {
runtimeOnly(org.neo4j:neo4j-jdbc-full-bundle:6.0.0) (1)
}
1 | The coordinates are the same for a Maven project. |
You can then use the Neo4j JDBC driver as you would do with any other JDBC driver:
In case any tooling asks you for the name of the concrete driver class, it is org.neo4j.jdbc.Neo4jDriver .
|
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.List;
public final class Quickstart {
static void queryWithCypher() {
var query = """
MATCH (m:Movie)<-[:ACTED_IN]-(p:Person)
RETURN m.title AS title, collect(p.name) AS actors
ORDER BY m.title
""";
var url = "jdbc:neo4j://localhost:7687";
var username = "neo4j";
var password = "verysecret";
try (var con = DriverManager.getConnection(url, username, password); (1)
var stmt = con.createStatement(); (2)
var result = stmt.executeQuery(query)) { (3)
while (result.next()) { (4)
var movie = result.getString(1); (5)
var actors = (List<String>) result.getObject("actors"); (6)
System.out.printf("%s%n", movie);
actors.forEach(actor -> System.out.printf("\t * %s%n", actor));
}
}
catch (SQLException ex) {
throw new RuntimeException(ex);
}
}
}
1 | Instantiate a JDBC connection. There’s no need to do any class loading beforehand, the driver will be automatically registered |
2 | Create a (reusable) statement |
3 | Execute a query |
4 | Iterate over the results, as with any other JDBC result set |
5 | JDBC’s indexing starts at 1 |
6 | JDBC also allows retrieval of result columns by name; the Neo4j JDBC driver also supports complex objects, such as lists |
In the example above we used Neo4j’s lingua franca, Cypher®, to query the database.
The Neo4j JDBC Driver has limited support for using SQL as well.
It can do so automatically or on a case-by-case basis.
To translate a single, call java.sql.Connection#nativeSQL(String)
and use the result in your queries.
For automatic translation, instantiate the driver setting the optional URL parameter sql2cypher
to true
.
The following example shows how:
var query = """
SELECT m.title AS title, collect(p.name) AS actors
FROM Person as p
JOIN Movie as m ON (m.id = p.ACTED_IN)
ORDER BY m.title
"""; (1)
var url = "jdbc:neo4j://localhost:7687?enableSQLTranslation=true";
try (var con = DriverManager.getConnection(url, username, password);
var stmt = con.createStatement();
var result = stmt.executeQuery(query)) {
while (result.next()) {
var movie = result.getString(1);
var actors = (List<String>) result.getObject("actors");
System.out.printf("%s%n", movie);
actors.forEach(actor -> System.out.printf("\t * %s%n", actor));
}
}
1 | This SQL query will be translated into the same Cypher query of the previous example. The remainder of the method is identical to before. |
For more information, see SQL to Cypher translation.
The JDBC specification does not support named parameters, only index-based parameters, starting at 1
.
So for all PreparedStatement
instances you need to specify parameters like this:
PreparedStatement
var cypher = "CREATE (m:Movie {title: $1})";
try (var con = DriverManager.getConnection(url, username, password);
PreparedStatement stmt = con.prepareStatement(cypher);) {
stmt.setString(1, "Test");
stmt.executeUpdate();
}
This is independent of the SQL-to-Cypher translation mechanism:
PreparedStatement
(SQL variant)var sql = "INSERT INTO Movie (title) VALUES (?)";
try (var con = DriverManager.getConnection(url + "?enableSQLTranslation=true", username, password);
PreparedStatement stmt = con.prepareStatement(sql);) {
stmt.setString(1, "Test");
stmt.executeUpdate();
}
To use named parameters, downcast the PreparedStatement
to Neo4jPreparedStatement
.
Neo4jPreparedStatement
var match = "MATCH (n:Movie {title: $title}) RETURN n.title AS title";
try (var con = DriverManager.getConnection(url, username, password);
Neo4jPreparedStatement stmt = (Neo4jPreparedStatement) con.prepareStatement(match);) {
stmt.setString("title", "Test");
try (var resultSet = stmt.executeQuery()) {
while (resultSet.next()) {
LOGGER.info(resultSet.getString("title"));
}
}
}
Getting a connection via environment variables
If you are happy to depend directly on org.neo4j.jdbc.Neo4jDriver
and want to get a connection as easy as possible, you might want to use fromEnv
:
// import org.neo4j.jdbc.Neo4jDriver;
try (var con = Neo4jDriver.fromEnv().orElseThrow(); (1)
var stmt = con.createStatement();
var result = stmt.executeQuery(query)) {
// Same loop as earlier
}
catch (SQLException ex) {
throw new RuntimeException(ex);
}
1 | Notice how we directly use the concrete driver class here and how the methods return an optional: no connection can be created if the required connection variables are not found. |
The fromEnv
method looks for a few specific system environment variables and it adheres to the 12 factor app principles:
-
First, it looks in the system environment
-
Second, it looks for a file named
.env
in the current working directory. There are overloads that let you configure the directory and the filename to look for.
The supported variables are:
NEO4J_URI
-
The address or URI of the instance to connect to.
NEO4J_USERNAME
-
(Optional) Username.
NEO4J_PASSWORD
-
(Optional) Password.
NEO4J_SQL_TRANSLATION_ENABLED
-
(Optional) Whether to enable full SQL-to-Cypher translation, defaults to
false
.
Information from both the system environment and the .env files are combined.
If for example NEO4J_SQL_TRANSLATION_ENABLED is in the system environment but not in the .env file, it will still be picked up.
Given the order of priority, information in the system environment always has precedence over the .env file.
|
This feature is especially useful with Neo4j AuraDB.
When creating a new AuraDB instance, you download a .env
file that you can directly use with the Neo4j JDBC Driver:
try (
var con = Neo4jDriver.fromEnv("Neo4j-cb3d8b2d-Created-2024-02-14.txt")
.orElseThrow();
var stmt = con.createStatement();
var movies = stmt.executeQuery("MATCH (n:Movie) RETURN n.title AS title")
) {
while (movies.next()) {
System.out.println(movies.getString("title"));
}
}