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:
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¶m2=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:
|
The driver accepts the following configuration arguments, either as properties or as URL query parameters:
Name | Type | Meaning | Default |
---|---|---|---|
|
|
Timeout for connection acquisition in milliseconds |
|
|
|
User agent |
|
|
|
Flag that enables automatic translation from SQL-to-Cypher (requires a translator on the classpath) |
|
|
|
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. |
|
|
|
Flag that allows you to use |
|
|
|
Optional flag, alternative to |
|
|
|
Optional configuration for fine-grained control over SSL configuration. Allowed values are |
|
|
|
The username (principal) to use for authentication. NOT RECOMMENDED as URL query parameter for security reasons. |
|
|
|
The password (credentials) to use for authentication. NOT RECOMMENDED as URL query parameter for security reasons. |
|
|
|
The realm to use for authentication. NOT RECOMMENDED as URL query parameter for security reasons. |
|
|
|
The authentication scheme to use. NOT RECOMMENDED as URL query parameter for security reasons. Currently supported values are:
|
|
|
|
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. |
|
|
|
A file, http or https URL pointing to a valid JSON file containing definitions for Cypher-backed views. |
|
|
|
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. |
|
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
.
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:
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
withssl=false
, orsslMode
set todisable
-
+ssc
withssl=false
, or anysslmode
not equal torequire
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 propertyauthn.supplier
is set to the name of the factory -
On the driver itself, when unwrapped to a
Neo4jDriver
, there isdriver#connect(String, Properties, Supplier)
. The given supplier will have precedence over any authentication information in theproperties
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 theDriverManager
-
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:
-
If there’s a supplier passed to
connect
it has precedence over the drivers locally configured supplier respectively the global one and any properties -
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 -
The supplier configured per driver or the global one has precedence over any properties
-
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.
|
KCAuthenticationSupplier
, as an example of an authentication supplier refreshing a token when necessaryimport 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:
<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:
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:
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:
Neo4jDriver
so that the connect
overload taking in an authentication supplier is visibleimport 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
:
.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:
.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:
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:
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
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 (eitherStatement
,PreparedStatement
orCallableStatement
) 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:
<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:
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:
Neo4jDataSource
spawning connections that trace their usageimport 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:
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:
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:
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:

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:
<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:
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:
