Configuration

Driver class name

The Neo4j JDBC Driver is org.neo4j.jdbc.Neo4jDriver. With modern Java tools, you should not need to touch this class directly, but there are some connection pools and front-ends that will ask you for this. The class is public API.

We also provide org.neo4j.jdbc.Neo4jDataSource as javax.sql.DataSource.

Causal clustering and bookmarks

The Neo4j JDBC Driver uses bookmarks by default to provide causal consistency in all Neo4j deployments. Bookmarks are managed on the driver level itself, not on the connections spawned by an instance of the driver, so all connections spawned by one instance will partake in the same causal chain of transactions. Connections from different driver instances will not use the same set of bookmarks and there is no built-in machinery that would enable this. If you want or need this, you can directly access the Neo4jDriver type to retrieve the current set of known bookmarks and pass them to another driver instance.

Neo4j transactional metadata

Neo4j supports attaching metadata to transactions, see SHOW TRANSACTIONS. As there is no explicit transaction object in the JDBC specification, the Neo4j JDBC driver needs another mechanism to make these configurable.

The JDBC driver provides the extension interface Neo4jMetadataWriter. Our driver, the connection implementation, and all statement variants can be unwrapped accordingly. The configuration is additive: metadata configured for a driver instance will be used for all connections spawned from that driver, connections can add further metadata, and statements can also add their own metadata. Metadata added on a statement has precedence over connection metadata which in turn has precedence over driver metadata:

Listing 1. Configuring transactional metadata
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.neo4j.jdbc.Neo4jDriver;
import org.neo4j.jdbc.Neo4jMetadataWriter;

public final class TransactionMetadata {

    private static final Logger LOGGER = Logger.getLogger(TransactionMetadata.class.getPackageName());

    public static void main(String... args) throws SQLException {
        var url = "jdbc:neo4j://localhost:7687";

        var driver = (Neo4jDriver) DriverManager.getDriver(url);
        driver.withMetadata(Map.of("md_from_driver", "v1", "will_be_overwritten", "irrelevant"));

        var properties = new Properties();
        properties.put("user", "neo4j");
        properties.put("password", "verysecret");

        try (
            var con = driver.connect(url, properties)
                .unwrap(Neo4jMetadataWriter.class)
                .withMetadata(Map.of("md_from_connection", "v2", "will_be_overwritten", "xxx"))
                .unwrap(Connection.class);
            var statement = con.createStatement()
                .unwrap(Neo4jMetadataWriter.class)
                .withMetadata(Map.of("md_from_stmt", "v3", "will_be_overwritten", "v4"))
                .unwrap(Statement.class)
        ) {
            try (var result = statement.executeQuery("SHOW TRANSACTIONS YIELD metaData")) {
                while (result.next()) {
                    var metaData = result.getObject("metaData", Map.class);
                    LOGGER.log(Level.INFO, "{0}", metaData);
                }
            }
        }
    }
}

The output will be similar to this:

Juli 17, 2024 1:18:16 PM org.neo4j.jdbc.docs.TransactionMetadata main
INFORMATION: {md_from_driver=v1, md_from_connection=v2, md_from_stmt=v3, will_be_overwritten=v4}

URL and connection properties

The canonical URL format for the Neo4j JDBC Driver is

jdbc:neo4j://<host>:<port>/<database>?param1=value1&param2=value2

The database name and all query parameters are optional and can be omitted. All configuration arguments can be passed either as query parameters or via a java.util.Properties object. The latter is sometimes defined by tooling for you.

With regard to authentication, we recommend to follow the JDBC specification, which discourages using any form of URL authentication. All query parameters must be percent-encoded if they contain special characters, e.g. …​?param1=space%20separated.

The driver supports the following URI schemes, which tweak the security configuration:

  • neo4j - No encryption.

  • neo4j+s - Enables encryption and only accepts SSL certificates from the server that are signed by a known Certificate Authority.

  • neo4j+ssc - Enables encryption and accepts self-signed certificates (which must be added to the certificate store).

bolt URI schemes are not supported.

The driver accepts the following configuration arguments, either as properties or as URL query parameters:

Table 1. Configuration arguments
Name Type Meaning Default

timeout

Integer

Timeout for connection acquisition in milliseconds

1000

agent

String

User agent

neo4j-jdbc

enableSQLTranslation

Boolean

Flag that enables automatic translation from SQL-to-Cypher (requires a translator on the classpath)

false

cacheSQLTranslations

Boolean

Flag that enables caching of translations. SQL translations are not "free": parsing of SQL costs a bit of time, and so does Cypher® rendering. In addition, we might up look up metadata to be able to project individual properties. If this takes too long, translations may be cached.

false

rewritePlaceholders

Boolean

Flag that allows you to use ? as placeholder in Cypher statements (as required by JDBC). These will automatically be rewritten into $1, $2$n, starting at 1, so that the numbering matches the 1-based JDBC index.

true when enableSQLTranslation is false, false otherwise

ssl

Boolean

Optional flag, alternative to neo4j+s. It can be used for example to programmatically enable the full SSL chain.

null

sslMode

Enum<SSLMode>

Optional configuration for fine-grained control over SSL configuration. Allowed values are disable, require, verify-full. See <ssl_mode, Understanding the SSL mode>>.

null

user

String

The username (principal) to use for authentication. NOT RECOMMENDED as URL query parameter for security reasons.

neo4j

password

String

The password (credentials) to use for authentication. NOT RECOMMENDED as URL query parameter for security reasons.

password

authRealm

String

The realm to use for authentication. NOT RECOMMENDED as URL query parameter for security reasons.

null

authScheme

String

The authentication scheme to use. NOT RECOMMENDED as URL query parameter for security reasons. Currently supported values are:

  • basic (default) for basic authentication.

  • bearer for bearer authentication (SSO). password should be set to the bearer token; user and authRealm have no effect.

  • kerberos for kerberos authentication. Requires password to be set to the kerberos ticket; user and authRealm have no effect.

  • none if authentication is disabled on the server. The properties user, password, authRealm have no effect.

basic

useBookmarks

boolean

Enables bookmark management for full causal cluster support. This is enabled by default and the recommended setting for all scenarios that use a connection pool. If you disable it, it will only be disabled for the specific connection. Other connections retrieved from the driver instance to the same or to other databases are not affected, and the individual connections will still manage their bookmarks.

true

viewDefinitions

String

A file, http or https URL pointing to a valid JSON file containing definitions for Cypher-backed views.

null

authn.supplier

String

The name of an authentication supplier, can be used to plugin various SSO systems such as Keycloak, with automatic token refresh. See below Authentication for more information.

null

Getting a driver or a connection instance

This section likely only applies if you use the Neo4j JDBC Driver as part of application development in contrast to using it as part of front-end tool such as DBeaver, DataGrip or UI-based ETL tools.

The easiest way to acquire a connection is directly through the java.sql.DriverManager.

Listing 2. Acquiring a JDBC connection to a Neo4j server
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;

class Configuration {

    void obtainConnection() throws SQLException {

        var url = "jdbc:neo4j://localhost:7687";
        var username = "neo4j";
        var password = "verysecret";
        var connection = DriverManager.getConnection(url, username, password);
    }

}

While our connection implementation is thread-safe, it allows only one concurrent transaction per connection (as dictated by the JDBC specification). For a multi-thread application, use a connection pool. There’s HikariCP, but usually application server and containers/frameworks bring their own. It’s safe to use any of them, as the Neo4j JDBC Driver does no internal pooling.

If you need access to an instance of the Neo4j driver itself, you can use the following approach:

Listing 3. Acquiring an instance of the Neo4j JDBC Driver
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;

class Configuration {

    void obtainDriverAndConnection() throws SQLException {

        var url = "jdbc:neo4j://localhost:7687";
        var driver = DriverManager.getDriver(url);

        var properties = new Properties();
        properties.put("username", "neo4j");
        properties.put("password", "verysecret");
        var connection = driver.connect(url, properties);
    }

}

Securing your connection by using SSL

The Neo4j JDBC Driver supports the same SSL option of the common Java driver, with the same URL protocols, using +s or +ssc as additional indicators for the required level of security.

The same configuration can also be achieved with a URL query parameter or an entry in the properties passed to the DriverManager or driver instance when asking for a connection. As long as you don’t specify contradicting values, it’s fine to combine both approaches.

Understanding the SSL mode

The following list is ordered by ascending security:

  • disable — (default), "I don’t care about security and don’t want to pay the overhead for encryption."

  • require — "I want my data to be encrypted, and I accept the overhead. I trust that the network will make sure I always connect to the server I want." (Server must support encryption, no hostname/CA validation is done. This saves the hassle of proper certificates and is only secure on private networks; it should not really be used over public networks.)

  • verify-full — "I want my data encrypted, and I accept the overhead. I want to be sure that I connect to a server I trust, and that it’s the one I specify."

The Neo4j JDBC Driver does not include revocation checks.

The most secure option can also be enabled by setting ssl=true either as query parameter or as property entry passed to the DriverManager. This option corresponds to neo4j+s. On the other hand, require corresponds to neo4j+ssc.

The additional enum allows us to possibly support additional modes in the future, such as letting the service decide about SSL, or being able to express a preference towards SSL without requiring it.

Neo4j servers can offer both plain Bolt connections and encrypted SSL connection, or just one of them. The fact that you can connect using neo4j+s does not mean that you cannot connect using just neo4j, or viceversa. This is dependent on the server setup. Neo4j Aura, Neo4j’s managed cloud service, only supports encrypted connections, so you must use +s, ssl=true, or sslMode=verify-full.

Valid URLs

The following URLs are all valid:

neo4j+s://xyz.databases.neo4j.io

Use full verification with the xzy instance at AuraDB

neo4j://xyz.databases.neo4j.io?ssl=true

The same, but using the shorthand URL parameter

neo4j://xyz.databases.neo4j.io?sslMode=verfiy-full

The same, but using the explicit mode

neo4j+s://xyz.databases.neo4j.io?ssl=true&sslMode=verify-full

Not more secure, but does not fail on you

neo4j+ssc://this.is.a.trustworthy.instance.for.sure.com

Trust whatever certificate and hostname there is, but do use SSL

neo4j://my-testing-instance.local

Use a plain connection.

The driver only refuses contradicting configurations, such as:

  • +s with ssl=false, or sslMode set to disable

  • +ssc with ssl=false, or any sslmode not equal to require

Basically, you cannot ask to use SSL and not use it at the same time. The driver offers several mechanism so that you can use a fixed URL with dynamic query parameters, or dynamic URLs, or whatever way of configuring you prefer in a programmatic way.

Using .dotenv files

When you create a Neo4j Aura instance, you will be asked to download a text-file named similar to Neo4j-9df57663-Created-2023-06-12.txt. This is essentially a .dotenv file containing the information required to connect to the database.

These files can be directly used via Neo4jDriver.fromEnv() (see Getting a connection via environment variables). This method exists in several overloads, which let you configure both filename and directory. Additionally, the builder lets you configure options that are not contained in the files from Aura.

Authentication

The Neo4j JDBC driver provides the ability to use a custom authentication supplier in addition to JDBCs standard ways of authentication, such as using java.sql.DriverManager#getConnection(java.lang.String, java.lang.String, java.lang.String) or java.sql.Driver#connect for authentication with the Neo4j server.

An authentication supplier is in Java terms a Supplier<org.neo4j.jdbc.authn.api.Authentication>. An Authentication instances is an abstract representation of an arbitrary authentication to be presented to the Neo4j server and can contain a principal and its credentials (username and password), a token or other ways of confirming an identity.

The interface itself offers the following factories:

Authentication#usernameAndPassword

Creates a simple username and password based authentication, which is what is used internally already with the default methods

Authentication#none

Disables authentication

Authentication#bearer

Creates a bearer-token based authentication

The latter allows to set an expiration date for the token. If the token expires, that is used after the expiration date, the supplier is asked for a new one. This allows configuring the Neo4j JDBC driver in such a way that it can grab tokens from an SSO provider, such as Keycloak or Okta, and authenticate itself with those tokens against Neo4j and automatically refresh those tokens when they expire.

In addition, there are two interfaces, CustomAuthentication and ExpiringAuthentication that can be implemented in case the above factories are not sufficient.

There are several ways to pass an authentication supplier:

  • Creating a library with your custom authentication supplier and a small factory for it implementing AuthenticationSupplierFactory: It will be automatically be loaded into the JDBC driver and be used when the JDBC property authn.supplier is set to the name of the factory

  • On the driver itself, when unwrapped to a Neo4jDriver, there is driver#connect(String, Properties, Supplier). The given supplier will have precedence over any authentication information in the properties parameter

  • Setting a supplier instance on a particular instance of the driver with driver#setAuthenticationSupplier(Supplier)

  • Registering a global supplier instance with Neo4jDriver#registerAuthenticationSupplier that will be used for all connections spawned from the DriverManager

  • The convenient builder methods present as static methods on Neo4jDriver allowing to configure the driver from the environment (see Getting a connection via environment variables) allow to specify the supplier, too

The precedence is as follows:

  1. If there’s a supplier passed to connect it has precedence over the drivers locally configured supplier respectively the global one and any properties

  2. If there’s a property named authn.supplier in the JDBC properties and an authentication supplier factory with that name is available, a supplier will be sourced from that factory and used

  3. The supplier configured per driver or the global one has precedence over any properties

  4. Properties will be used as per JDBC standard when there is no explicit or implicit authentication supplier

The driver will only call the supplier when no token has been retrieved yet or when the first token expires. Any authentication supplier implementation can safely assume that the second call for a token means the first token is expired, so that the supplier does not need to manage state.

The underlying bolt protocol and the Neo4j Server might not work reliable with very short-lived authentication tokens (i.e. with a lifespan under 5 seconds). This restriction might be lifted in the future, but as of release 6.6.0 you might want to refrain from very short-lived tokens.

Keycloak example

The following class shows an example based on Keycloaks AuthzClient. The supplier will grab tokens from a Keycloak instance and store their expiration date along with the JWT token. The initial ask for a token will always be answered with a completely new one, each follow-up call will use the client to refresh the token.

There is no need to copy the below code into your own application. We actually do ship this authentication supplier with the optional org.neo4j:neo4j-jdbc-authn-kc module. The supplier is part of our semver api contract and can either be used directly or via the service loader mechanism, which is also demonstrated in the following documentation.
Listing 4. KCAuthenticationSupplier, as an example of an authentication supplier refreshing a token when necessary
import java.io.IOException;
import java.io.UncheckedIOException;
import java.time.Instant;
import java.util.Base64;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;

import com.fasterxml.jackson.jr.ob.JSON;
import org.keycloak.authorization.client.AuthzClient;
import org.keycloak.authorization.client.Configuration;
import org.keycloak.authorization.client.util.Http;
import org.keycloak.representations.AccessTokenResponse;
import org.neo4j.jdbc.authn.spi.Authentication;

public final class KCAuthenticationSupplier implements Supplier<Authentication> {

    public static Supplier<Authentication> of(String user, String password, Configuration configuration) {
        return new KCAuthenticationSupplier(user, password, configuration);
    }

    private final String username;

    private final String password;

    private final Configuration cfg;

    private final AuthzClient authzClient;

    private final Http http;

    private final String url;

    private final AtomicReference<TokensAndExpirationTime> currentToken = new AtomicReference<>();

    KCAuthenticationSupplier(String user, String password, Configuration cfg) {
        this.username = user;
        this.password = password;
        this.cfg = cfg;
        this.authzClient = AuthzClient.create(cfg);
        this.url = "%s/realms/%s/protocol/openid-connect/token".formatted(cfg.getAuthServerUrl(), cfg.getRealm());
        this.http = new Http(cfg, cfg.getClientCredentialsProvider());
    }

    @Override
    public Authentication get() {
        return this.currentToken.updateAndGet(previous -> {
            if (previous == null) {
                return get0();
            }
            return refresh0(previous.refreshToken());
        }).toAuthentication();
    }

    TokensAndExpirationTime get0() {
        try {
            return TokensAndExpirationTime.of(this.authzClient.obtainAccessToken(this.username, this.password));
        }
        catch (IOException ex) {
            throw new UncheckedIOException(ex);
        }
    }

    TokensAndExpirationTime refresh0(String refreshToken) {
        try {
            return TokensAndExpirationTime.of(this.http.<AccessTokenResponse>post(this.url)
                .authentication()
                .client()
                .form()
                .param("grant_type", "refresh_token")
                .param("refresh_token", refreshToken)
                .param("client_id", this.cfg.getResource())
                .param("client_secret", (String) this.cfg.getCredentials().get("secret"))
                .response()
                .json(AccessTokenResponse.class)
                .execute());
        }
        catch (IOException ex) {
            throw new UncheckedIOException(ex);
        }
    }

    record TokensAndExpirationTime(String accessToken, Instant expiresAt, String refreshToken) {

        static final Base64.Decoder DECODER = Base64.getDecoder();

        static TokensAndExpirationTime of(AccessTokenResponse accessTokenResponse) throws IOException {
            var chunks = accessTokenResponse.getToken().split("\\.");
            var payload = JSON.std.mapFrom(DECODER.decode(chunks[1]));
            var exp = Instant.ofEpochSecond(((Number) payload.get("exp")).longValue());
            return new TokensAndExpirationTime(accessTokenResponse.getToken(), exp,
                    accessTokenResponse.getRefreshToken());
        }

        Authentication toAuthentication() {
            return Authentication.bearer(this.accessToken, this.expiresAt);
        }

    }

}

For using the above supplier add the following dependency to your class- or module-path:

Listing 5. Maven dependency for the core Neo4j JDBC Driver artifact
<dependency>
    <groupId>org.neo4j</groupId>
    <artifactId>neo4j-jdbc-authn-kc</artifactId>
    <version>6.6.0</version>
</dependency>

The same would apply for any custom supplier you might write. The easiest way to use the supplier now is via the build-in loader, so that you can stick purely to the JDBC api:

Listing 6. Using a custom authentication supplier via the authn.supplier config option:
@Test
void authenticationSupplierFactoriesShouldWork() throws SQLException {

    var properties = new Properties();
    properties.put("enableSQLTranslation", "true");

    properties.put("user", "william.foster");
    properties.put("password", "d-fens");

    properties.put("authn.supplier", "kc"); (1)
    properties.put("authn.kc.authServerUrl", "http://localhost:%d".formatted(KEYCLOAK.getHttpPort())); (2)
    properties.put("authn.kc.realm", "neo4j-sso-test");
    properties.put("authn.kc.clientId", "neo4j-jdbc-driver");
    properties.put("authn.kc.clientSecret", "QcWXnTg8qJpVMnIvm8Ev8gp1PqJitZu4");

    try (var connection = DriverManager.getConnection(neo4jUri, properties); (3)
            var stmt = connection.createStatement();
            var rs = stmt.executeQuery("SELECT 1")) {
        assertThat(rs.next()).isTrue();
        assertThat(rs.getInt(1)).isOne();
    }
}
1 Indicate that you want to use the Keycloak authentication supplier (named kc in our distribution)
2 This and the following properties will be passed to the authentication system so that it can create authentication with Keycloak
3 This is a pure JDBC spec call

In case you don’t want to use the automatic loader for authentication supplier factories, or you did write a completely custom authentication supplier and don’t want to provide services and corresponding module descriptions, there are several other ways of using an authentication supplier.

The most JDBC complaint way is registering it for the global driver like this:

Listing 7. Registering an instance of the authentication supplier globally
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.function.Supplier;

import org.keycloak.authorization.client.Configuration;
import org.neo4j.jdbc.authn.kc.KCAuthenticationSupplier;
import org.neo4j.jdbc.authn.spi.Authentication;
import org.neo4j.jdbc.Neo4jDriver;

class Example {

	public static void main(String...a) throws Exception {
		Supplier<Authentication> authenticationSupplier = KCAuthenticationSupplier.of("user", "pass", new Configuration());
        Neo4jDriver.registerAuthenticationSupplier(authenticationSupplier);
        Connection connection = DriverManager.getConnection("neo4j+s://xyz.databases.neo4j.io");
    }
}

The advantage here is that it makes sure that all applications that use the Neo4j JDBC Driver in a JDBC compliant way over the DriverManager will automatically use the authentication supplier. Hence, the above setting is most likely the easiest way for integrations with frameworks like Spring Boot, in which you could configure the global supplier through an org.springframework.beans.factory.InitializingBean. Alternatively use the driver builder or manually create an instance of the driver:

Listing 8. Creating an explicit instance of Neo4jDriver so that the connect overload taking in an authentication supplier is visible
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.Properties;
import java.util.function.Supplier;

import org.keycloak.authorization.client.Configuration;
import org.neo4j.jdbc.authn.kc.KCAuthenticationSupplier;
import org.neo4j.jdbc.authn.spi.Authentication;
import org.neo4j.jdbc.Neo4jDriver;

class Example {

    public static void main(String...a) throws Exception {
        Supplier<Authentication> authenticationSupplier = KCAuthenticationSupplier.of("user", "pass", new Configuration());
        Neo4jDriver.registerAuthenticationSupplier(authenticationSupplier);
        Neo4jDriver driver = new Neo4jDriver();
        Connection connection = driver.connect("neo4j+s://xyz.databases.neo4j.io", new Properties(), authenticationSupplier);
    }
}

This alternative is feasible in any application for which you are in sole control of creating connections.

If you need to against Keycloak, the Neo4j JDBC driver does ship everything you need, and you are good to go. Just include the Maven module and apply the recommended way of configuring the supplier via JDBC properties. For all other authentication needs you can write a custom authentication supplier in a similar fashion as the provided Keycloak support. Have a look at the JavaDoc of org.neo4j.jdbc.authn.spi.AuthenticationSupplierFactory in the org.neo4j:neo4j-jdbc-authn-spi.

Logging

Via standard Java settings

To generate a useful and detailed log, you must configure java.util.logging. This can be done either through a configuration file or programmatically. The following section includes an example configuration that sets the global logging level to INFO, Neo4j JDBC Driver itself to DEBUG and everything that is network related to WARN:

Listing 9. Logging configuration for the Neo4j JDBC Driver that logs all information
.level = INFO
handlers=java.util.logging.ConsoleHandler

# The handler must be configured for the lowest level it should catch
java.util.logging.ConsoleHandler.level=FINEST

# Enables all Logging for the JDBC Driver
org.neo4j.jdbc.level=ALL
# But reduces the noise on the network and the result set
org.neo4j.jdbc.result-set.level=WARNING
org.neo4j.jdbc.network.level=WARNING
# This is an example how to configure the outbound and inbound messages
org.neo4j.jdbc.network.OutboundMessageHandler.level=FINE
org.neo4j.jdbc.network.InboundMessageHandler.level=FINE

# This produces a date, followed by the level, 3 dashes, the message and the source of the log if available
java.util.logging.SimpleFormatter.format = %1$tFT%1$tk:%1$tM:%1$tS.%1$tL%1$tz %4$-15s --- %5$s [%2$s]%n

Such a file must be passed to the running Java program as a system property to Java itself with:

java -Djava.util.logging.config.file=docs/src/main/resources/logging-to-console.properties <your program>

Capturing logs to a file requires to change the handler:

Listing 10. Logging configuration for the Neo4j JDBC Driver that writes all JDBC logs to a file
.level = INFO
handlers=java.util.logging.ConsoleHandler

# Keep everything by default at INFO
java.util.logging.ConsoleHandler.level=INFO

# The handler must be configured for the lowest level it should catch
java.util.logging.FileHandler.level=FINEST
java.util.logging.FileHandler.pattern=jdbc.log
# XML Format by default
# java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter

# Change the handler for JDBC
org.neo4j.jdbc.handlers=java.util.logging.FileHandler
# Keep the rest as above
org.neo4j.jdbc.level=ALL
org.neo4j.jdbc.result-set.level=WARNING
org.neo4j.jdbc.network.level=WARNING
org.neo4j.jdbc.network.OutboundMessageHandler.level=FINE
org.neo4j.jdbc.network.InboundMessageHandler.level=FINE

java.util.logging.SimpleFormatter.format = %1$tFT%1$tk:%1$tM:%1$tS.%1$tL%1$tz %4$-15s --- %5$s [%2$s]%n
The diagnosability features of the JDBC driver are based on the standard java.util.logging framework. Configuration of that framework itself is out of scope for this documentation. For a complete overview you might want to start at "Java Logging Overview". The formatter Java uses is the java.util.Formatter.

Inside a Spring Boot application

The Neo4j JDBC Driver will integrate itself into the Spring Boot logging configuration and take all relevant format and other settings from there. The levels can be configured with the levels Spring does understand. The following configuration is equivalent to the one above:

Listing 11. Logging configuration for the Neo4j JDBC Driver in a Spring Boot configuration file
logging.level.org.neo4j.jdbc=trace
logging.level.org.neo4j.jdbc.result-set=warn
logging.level.org.neo4j.jdbc.network=warn
logging.level.org.neo4j.jdbc.network.OutboundMessageHandler=debug
logging.level.org.neo4j.jdbc.network.InboundMessageHandler=debug

Inside a Quarkus application

The Neo4j JDBC Driver will integrate itself into the Quarkus logging configuration and take all relevant format and other settings from there. The levels can be configured with the levels Quarkus does understand. The following configuration is equivalent to the ones above:

Listing 12. Logging configuration for the Neo4j JDBC Driver in a Quarkus configuration file
quarkus.log.category."org.neo4j.jdbc".min-level=TRACE
quarkus.log.category."org.neo4j.jdbc".level=TRACE
quarkus.log.category."org.neo4j.jdbc.result-set".level=DEBUG
quarkus.log.category."org.neo4j.jdbc.network".level=WARNING
quarkus.log.category."org.neo4j.jdbc.network.OutboundMessageHandler".level=DEBUG
quarkus.log.category."org.neo4j.jdbc.network.InboundMessageHandler".level=DEBUG

SQL Translations

The SQL translators use the logger org.neo4j.jdbc.translator; processed SQL and its original sources is logged at level FINE on org.neo4j.jdbc.statement.SQL.

Metrics

The Neo4j JDBC Driver will publish metrics via Micrometer. If you put Micrometer on your classpath, no further configuration is necessary. This is for example automatically the case with Spring Boot Actuator or Micrometer for Quarkus. To make best use of this feature, you will additionally configure your monitoring system.

The Neo4j JDBC Driver does publish the following metrics:

org.neo4j.jdbc.connections

a gauge representing the number of open connections, tagged with url to which the driver is connected

org.neo4j.jdbc.statements

a gauge representing the number of open statements, tagged with both the url to which the statement is opened and the JDBC type of the statement (either Statement, PreparedStatement or CallableStatement )

org.neo4j.jdbc.queries

a composite meter containing the counts of successful and failed queries and a timer measuring the duration of queries

org.neo4j.jdbc.cached-translations

a gauge representing the number of cached SQL to cypher translations

Tracing

The Neo4j JDBC Driver supports tracing and will provide traces spans for the following operations on JDBC Statement instances:

  • #execute

  • #executeQuery

  • #executeUpdate

and all overloads thereof and in all inheriting types. In addition, when using a JDBC ResultSet, it will open a span when the result set is first moved from being before the first row until it is either moved beyond the last row or actively closed. Spans will contain various events, such as when a query has been actually executed by the Neo4j database or when a batch of records has been pulled from the database.

The feature is optional: when you want to use it, you have to bring in additional dependencies. This applies to all distributed versions, including the full bundles, so that an optional feature doesn’t make the bundles bigger. For Maven, use this additional dependency declaration:

Listing 13. Maven dependency that allows you to configure tracing
<dependency>
    <groupId>org.neo4j</groupId>
    <artifactId>neo4j-jdbc-tracing-micrometer</artifactId>
    <version>6.6.0</version>
</dependency>

And for Gradle you would want to declare the following runtime dependency:

Listing 14. Gradle dependency that allows you to configure tracing
dependencies {
    runtimeOnly 'org.neo4j:neo4j-jdbc-tracing-micrometer:6.6.0'
}

This dependency brings in Micrometer Tracing. Micrometer Tracing allows you to bind to various tracers and exporters, such as Zipkin or OpenTelemetry.

Once you have a Micrometer Tracer, you can configure a Neo4jDataSource like this:

Listing 15. A Neo4jDataSource spawning connections that trace their usage
import javax.sql.DataSource;

import org.neo4j.jdbc.Neo4jDataSource;
import org.neo4j.jdbc.tracing.micrometer.Neo4jTracingBridge;

import io.micrometer.tracing.Tracer;

public class TracingExampleSetup {

    DataSource neo4jDataSource() {

        Tracer tracer = Tracer.NOOP; (1)
        var neo4jDataSource = new Neo4jDataSource();
        neo4jDataSource.setUrl("jdbc:neo4j://yourhost:7687");
        neo4jDataSource.setPassword("neo4j");
        neo4jDataSource.setUser("verysecret");
        neo4jDataSource.setTracer(Neo4jTracingBridge.to(tracer)); (2)
        return neo4jDataSource;
    }
}
1 In most scenarios, your application framework will provide a Micrometer tracer instance for you
2 With this call you enable tracing in the data source and all connections spawned from it

You can also enable tracing on individual connections by unwrapping them into the Neo4jConnection extension like this:

Listing 16. Enable tracing for an individual connection
import java.sql.Connection;
import java.sql.DriverManager;

import org.neo4j.jdbc.Neo4jConnection;
import org.neo4j.jdbc.tracing.micrometer.Neo4jTracingBridge;

import io.micrometer.tracing.Tracer;

public class TracingExampleSetup {

    Connection connection() {

        Tracer tracer = Tracer.NOOP;
        Neo4jConnection connection = DriverManager.getConnection("jdbc:neo4j://yourhost:7687", "neo4j", "verysecret")
            .unwrap(Neo4jConnection.class); (1)
        connection.setTracer(Neo4jTracingBridge.to(tracer)); (2)
    }
}
1 Unwrap the connection into our extension so that you can configure the tracer
2 Configure the tracer
In case you neither want to work with a data source nor individual connections, use Neo4jDriver.registerTracer(Neo4jTracingBridge.to(tracer)) to register the tracer with all Neo4j drivers that are registered with the JDBC DriverManager.

Using tracing with Spring Boot

The tracing implementation that is provided by the Neo4j JDBC Driver utilizes Micrometer Tracing, which is well accepted in the Spring Boot ecosystem, but also in Quarkus, Micronaut and others. We provide a few examples, but refer to your frameworks documentation on how to configure tracing.

If you are happy to configure tracing for all connections acquired from the DriverManager, configure tracing like this:

Listing 17. Configure tracing for all drivers registered with DriverManager
import io.micrometer.tracing.Tracer;
import org.neo4j.jdbc.tracing.micrometer.Neo4jTracingBridge;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class TracingConfiguration {

    @Bean
    InitializingBean neo4jTracingInitializer(Tracer tracer) {
        return () -> Neo4jDriver.registerTracer(Neo4jTracingBridge.to(tracer));
    }
}

Otherwise, use a custom datasource as shown below.

Configuring the tracing datasource

Usually, Spring Boot configures a HikariDataSource based on you driver. Hikari provides connection pooling, and you should not ditch that. There’s no need to create your own data source properties for all configuration values, you can just inject it like this:

Listing 18. Configure a tracing and pooled data source with Spring Boot
import javax.sql.DataSource;

import org.neo4j.jdbc.Neo4jDataSource;
import org.neo4j.jdbc.tracing.micrometer.Neo4jTracingBridge;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import io.micrometer.tracing.Tracer;

@Configuration
class TracingTestConfiguration {

    @Bean
    DataSource neo4jDataSource(Tracer tracer, DataSourceProperties dataSourceProperties) {

        var neo4jDataSource = new Neo4jDataSource();
        neo4jDataSource.setUrl(dataSourceProperties.getUrl());
        neo4jDataSource.setPassword(dataSourceProperties.getPassword());
        neo4jDataSource.setUser(dataSourceProperties.getUsername());
        neo4jDataSource.setTracer(Neo4jTracingBridge.to(tracer));

        var cfg = new HikariConfig();
        cfg.setDataSource(neo4jDataSource);
        return new HikariDataSource(cfg);
    }
}

Bring org.springframework.boot:spring-boot-starter-actuator onto your class path too, and it will configure Micrometer for you.

The setup of your data source stays the same: no change is required here for the following examples to work, as our Tracing API will automatically pick the correct tracer through Micrometer!

Exporting to Zipkin

To export your traces to Zipkin, add io.micrometer:micrometer-tracing-bridge-brave and io.zipkin.reporter2:zipkin-reporter-brave and set management.zipkin.tracing.export.enabled to true in your Spring application.

You should see spans like this:

zipkintracing

Exporting via OpenTelemetry

OpenTelemetry allows to export not only traces, but metrics and logging too.

Here’s an example how to use Dash0 via OpenTelemetry. The exporter should work the same for other services. OpenTelemetry has split its SDK into several modules, hence you will first import their BOM into your build file. Here’s an example for Maven:

Listing 19. Snippet from a Maven build file to include the OpenTelemetry BOM
<dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-bom</artifactId>
    <version>1.47.0</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>

First you need to bring in the bridge from Micrometer to OpenTelemetry via: io.micrometer:micrometer-tracing-bridge-otel, so that Micrometer pushes into that SDK. Next, bring in the following dependencies so that Spring Boot can configure the exporter:

  • io.opentelemetry:opentelemetry-sdk

  • io.opentelemetry:opentelemetry-exporter-common

  • io.opentelemetry:opentelemetry-exporter-otlp

  • io.opentelemetry:opentelemetry-exporter-sender-jdk

In the case of Dash0, the application configuration will look similar to this:

Listing 20. Using the OpenTelemetry exporter in Spring Boot towards Dash0
management.otlp.tracing.export.enabled=true
management.otlp.tracing.endpoint=https://ingress.eu-west-1.aws.dash0.com/v1/traces
management.otlp.tracing.headers.Authorization=Bearer auth_XX_your_token_XX

management.otlp.metrics.export.step=30s
management.otlp.metrics.export.enabled=true
management.otlp.metrics.export.url=https://ingress.eu-west-1.aws.dash0.com/v1/metrics
management.otlp.metrics.export.headers.Authorization=Bearer auth_XX_your_token_XX

The trace from our Movie application looks like this:

dash0tracing