Usage

Add the JDBC driver to your application, for example as a Gradle dependency:

Listing 1. Using the full bundle as a runtime dependency inside a Gradle-based project
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.
Listing 2. Acquire a connection and execute a query
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:

Listing 3. Configure the JDBC driver to automatically translate SQL to cypher.
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:

Listing 4. Using parameters with a 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:

Listing 5. Using parameters with a 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.

Listing 6. Using named parameters with a 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:

Listing 7. Get a connection from environment variables
// 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:

Listing 8. Using a .env file from AuraDB
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"));
    }
}