Claude
Skills
Sign in
Back

neo4j-driver-java-skill

Included with Lifetime
$97 forever

Neo4j Java Driver v6 — driver lifecycle, Maven/Gradle setup, executableQuery, executeRead/Write managed transactions, explicit transactions, async/reactive patterns, error handling, data type mapping, connection pool tuning, causal consistency/bookmarks. Use when writing Java or Kotlin code that connects to Neo4j via GraphDatabase.driver, executableQuery, SessionConfig, executeRead, executeWrite, or TransactionCallback. Does NOT handle Cypher authoring — use neo4j-cypher-skill. Does NOT cover driver version upgrades — use neo4j-migration-skill. Does NOT cover Spring Data Neo4j (@Node, Neo4jRepository) — use neo4j-spring-data-skill.

Backend & APIs

What this skill does


## When to Use
- Java/Kotlin code connecting to Neo4j (Aura or self-managed)
- Setting up driver, sessions, transactions in Maven/Gradle projects
- Debugging result handling, error recovery, connection pool issues
- Async (`CompletableFuture`) or reactive (Project Reactor / RxJava) Neo4j access

## When NOT to Use
- **Cypher query authoring/optimization** → `neo4j-cypher-skill`
- **Driver version upgrades** → `neo4j-migration-skill`
- **Spring Data Neo4j** (`@Node`, `@Relationship`, `Neo4jRepository`) → `neo4j-spring-data-skill`

---

## Dependency

### Maven
```xml
<dependency>
    <groupId>org.neo4j.driver</groupId>
    <artifactId>neo4j-java-driver</artifactId>
    <version>6.1.0</version>
</dependency>
```

### Gradle
```groovy
implementation 'org.neo4j.driver:neo4j-java-driver:6.1.0'
```

Check latest: https://central.sonatype.com/artifact/org.neo4j.driver/neo4j-java-driver

---

## Environment Variables

Standard pattern for connection config — never hardcode credentials:

```java
String uri      = System.getenv().getOrDefault("NEO4J_URI",      "neo4j://localhost:7687");
String user     = System.getenv().getOrDefault("NEO4J_USERNAME",  "neo4j");
String password = System.getenv().getOrDefault("NEO4J_PASSWORD",  "");
String database = System.getenv().getOrDefault("NEO4J_DATABASE",  "neo4j");
```

Spring Boot: inject via `@Value("${spring.neo4j.uri}")` or `application.properties`:
```properties
spring.neo4j.uri=neo4j+s://xxx.databases.neo4j.io
spring.neo4j.authentication.username=neo4j
spring.neo4j.authentication.password=secret
```

---

## Driver Lifecycle

One `Driver` per application — thread-safe, expensive to create. Implement `AutoCloseable` or use try-with-resources.

```java
// Long-lived singleton
var driver = GraphDatabase.driver(
    "neo4j+s://xxx.databases.neo4j.io",          // Aura TLS+routing
    AuthTokens.basic(user, password));
driver.verifyConnectivity();                      // fail fast

// Short-lived (tests / CLI)
try (var driver = GraphDatabase.driver(uri, AuthTokens.basic(user, password))) {
    driver.verifyConnectivity();
    // ...
}
```

URI schemes:

| URI | Use |
|---|---|
| `neo4j://localhost` | Unencrypted, cluster routing |
| `neo4j+s://xxx.databases.neo4j.io` | TLS + cluster routing (Aura) |
| `bolt://localhost:7687` | Unencrypted, single instance |
| `bolt+s://localhost:7687` | TLS, single instance |

Auth options: `AuthTokens.basic(u,p)` · `AuthTokens.bearer(token)` · `AuthTokens.kerberos(b64)` · `AuthTokens.none()`

---

## Choosing the Right API

| API | When | Auto-retry | Streaming |
|---|---|:---:|:---:|
| `driver.executableQuery()` | Default for most queries | ✅ | ❌ eager |
| `session.executeRead/Write()` | Large results, callback control | ✅ | ✅ |
| `session.beginTransaction()` | Multi-method, external coordination | ❌ | ✅ |
| `session.run()` | Self-managing queries (`CALL IN TRANSACTIONS`) | ⚠️ one-shot [6.1+] | ✅ |
| `driver.asyncSession()` | Non-blocking `CompletableFuture` | ✅ | ✅ |
| `driver.rxSession()` | Reactor/RxJava backpressure | ✅ | ✅ |

`CALL { … } IN TRANSACTIONS` and `USING PERIODIC COMMIT` self-manage their transaction — use `session.run()` only. `executableQuery` and `executeRead/Write` will fail for these queries.

`session.run()` retry [6.1+]: single immediate retry on idempotent errors only (enabled by default). Disable per driver or per session:

```java
// Driver-level — disable for all sessions
var config = Config.builder().withAutoCommitRetriesDisabled(true).build();

// Session-level — overrides driver
var sessionConfig = SessionConfig.builder()
    .withAutoCommitRetriesMode(AutoCommitRetriesMode.DISABLED)  // DEFAULT = follow driver
    .build();
```

---

## `executableQuery` — Default

```java
// Read — route to replicas
var result = driver.executableQuery("""
        MATCH (p:Person {name: $name})-[:KNOWS]->(friend)
        RETURN friend.name AS name
        """)
    .withParameters(Map.of("name", "Alice"))
    .withConfig(QueryConfig.builder()
        .withDatabase("neo4j")            // always specify — avoids home-db round-trip
        .withRouting(RoutingControl.READ)
        .build())
    .execute();

result.records().forEach(r -> System.out.println(r.get("name").asString()));
long ms = result.summary().resultAvailableAfter(TimeUnit.MILLISECONDS);

// Write
driver.executableQuery("CREATE (p:Person {name: $name, age: $age})")
    .withParameters(Map.of("name", "Bob", "age", 30))
    .withConfig(QueryConfig.builder().withDatabase("neo4j").build())
    .execute();
```

Never string-interpolate Cypher. Always `.withParameters(Map.of(...))`.

---

## Managed Transactions (`executeRead` / `executeWrite`)

Sessions are NOT thread-safe — one per request/thread, always close.

```java
try (var session = driver.session(SessionConfig.builder()
        .withDatabase("neo4j").build())) {

    // Read → replica routing
    var names = session.executeRead(tx -> {
        var result = tx.run(
            "MATCH (p:Person) WHERE p.name STARTS WITH $prefix RETURN p.name AS name",
            Map.of("prefix", "Al"));
        return result.stream().map(r -> r.get("name").asString()).toList(); // collect INSIDE
    });

    // Write → leader routing
    session.executeWriteWithoutResult(tx ->
        tx.run("CREATE (p:Person {name: $name})", Map.of("name", "Carol"))
    );
}
```

### Result must be consumed INSIDE the callback

`Result` is a lazy cursor tied to the open transaction. Transaction closes when callback returns — any read after that throws `ResultConsumedException`.

```java
// ❌ Returns Result — already closed by the time caller uses it
var result = session.executeRead(tx ->
    tx.run("MATCH (p:Person) RETURN p.name AS name"));
result.stream().forEach(...); // throws ResultConsumedException

// ✅ Collect to List inside callback
var names = session.executeRead(tx ->
    tx.run("MATCH (p:Person) RETURN p.name AS name")
      .stream().map(r -> r.get("name").asString()).toList());
```

### Callback rules

- Consume each `Result` before next `tx.run()` — multiple open cursors = undefined behaviour.
- No side effects (HTTP, email, metric increments) — callback may be retried on transient errors.
- Use `MERGE` (idempotent), not `CREATE`, for retry-safe writes.
- `executeRead` → replica; `executeWrite` → leader.

### TransactionConfig — timeouts & metadata

```java
var config = TransactionConfig.builder()
    .withTimeout(Duration.ofSeconds(5))
    .withMetadata(Map.of("app", "myService", "user", userId))  // visible in SHOW TRANSACTIONS
    .build();
session.executeRead(tx -> { /* ... */ }, config);
```

---

## Explicit Transactions

Use when work spans multiple methods or requires external coordination. Not auto-retried.

```java
try (var session = driver.session(SessionConfig.builder().withDatabase("neo4j").build())) {
    var tx = session.beginTransaction();
    try {
        doPartA(tx);
        doPartB(tx);
        tx.commit();
    } catch (Exception e) {
        try { tx.rollback(); } catch (Exception rb) { e.addSuppressed(rb); }
        throw e;
    }
}
```

`tx.rollback()` is a network call — wrap in its own try/catch and use `addSuppressed` so the original exception is not lost.

**Commit uncertainty**: if `tx.commit()` throws `ServiceUnavailableException`, the commit may or may not have succeeded. Design writes as idempotent (`MERGE` + unique constraints) so retrying is safe.

Choose explicit vs managed:
- Auto-retry needed → `executeRead` / `executeWrite`
- Work spans multiple methods → explicit (pass `tx` as parameter)
- Coordinating with external I/O → explicit (commit only after I/O succeeds)

---

## Error Handling

```java
try {
    driver.executableQuery("...").execute();
} catch (ServiceUnavailableException e) {
    // No servers — check connection
} catch (SessionExpiredException e) {
    // Server closed session — open new one
} catch (TransientException e) {
    // Managed txns retry automatically; explicit txns need manual retry
} catch (Neo4jException e)

Related in Backend & APIs