
Neo4j Driver Java Skill
- 331 installs
- 101 repo stars
- Updated August 3, 2026
- neo4j-contrib/neo4j-skills
Implement correct Neo4j Java Driver v6 patterns—transactions, async, reactive, and batch writes—in a Spring-free or plain Java/Kotlin service.
About
Neo4j-driver-java-skill teaches agents how to write production-grade Java and Kotlin against the official Neo4j Java Driver v6. Solo builders and small teams reach for it when a product needs graph relationships—recommendations, permissions, knowledge graphs—without guessing transaction boundaries or leaking sessions. The skill spans dependency setup, managed and explicit transactions, async and reactive entry points, and practical mapping rules such as null-safety with asString and containsKey. It deliberately excludes Cypher authoring, version migrations, and Spring Data Neo4j, pointing to sibling skills for those concerns so the agent does not mix ORM patterns with the low-level driver. Compatibility is stated as Driver v6, Java 17+, and Kotlin 1.9+. Use it during backend implementation when stack traces involve ServiceUnavailableException, TransientException, or incorrect parameter typing in UNWIND batches.
- Maven/Gradle dependency setup and verifyConnectivity lifecycle
- executableQuery as the recommended default API
- executeRead/executeWrite managed transactions with explicit rollback and commit-uncertainty rules
- Async (CompletableFuture) and reactive (Project Reactor RxSession) APIs
- UNWIND batch writes, pool tuning, bookmarks, and Neo4jException error taxonomy
Neo4j Driver Java Skill by the numbers
- 331 all-time installs (skills.sh)
- +25 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #164 of 911 Databases skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/neo4j-contrib/neo4j-skills --skill neo4j-driver-java-skillAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 331 |
|---|---|
| repo stars | ★ 101 |
| Security audit | 3 / 3 scanners passed |
| Last updated | August 3, 2026 |
| Repository | neo4j-contrib/neo4j-skills ↗ |
What it does
Implement correct Neo4j Java Driver v6 patterns—transactions, async, reactive, and batch writes—in a Spring-free or plain Java/Kotlin service.
Files
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
<dependency>
<groupId>org.neo4j.driver</groupId>
<artifactId>neo4j-java-driver</artifactId>
<version>6.1.0</version>
</dependency>Gradle
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:
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:
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.
// 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:
// 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
// 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.
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.
// ❌ 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
Resultbefore nexttx.run()— multiple open cursors = undefined behaviour. - No side effects (HTTP, email, metric increments) — callback may be retried on transient errors.
- Use
MERGE(idempotent), notCREATE, for retry-safe writes. executeRead→ replica;executeWrite→ leader.
TransactionConfig — timeouts & metadata
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.
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
txas parameter) - Coordinating with external I/O → explicit (commit only after I/O succeeds)
---
Error Handling
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) {
// Cypher/constraint error — e.code() gives GQL status code
}Managed transactions auto-retry TransientException — no catch needed.
---
Data Types & Value Extraction
| Cypher type | Java accessor |
|---|---|
Integer | value.asLong() / value.asInt() |
Float | value.asDouble() |
String | value.asString() |
Boolean | value.asBoolean() |
List | value.asList() |
Map | value.asMap() |
Node | value.asNode() |
Relationship | value.asRelationship() |
Date | value.asLocalDate() |
DateTime | value.asZonedDateTime() |
var record = result.records().get(0);
String name = record.get("name").asString();
long age = record.get("age").asLong();
var node = record.get("p").asNode();
String label = node.labels().iterator().next();
Map<String,Object> props = node.asMap();Null safety — two distinct cases
| Situation | record.get(key) | .asString() |
|---|---|---|
| Key present, value non-null | the value | returns string |
| Key present, value is graph null | Value where .isNull() = true | throws Uncoercible |
| Key absent (typo / not projected) | Value.NULL sentinel | throws NoSuchElementException |
// Graph null — use default overload (safe only if key is always projected):
String city = record.get("city").asString("Unknown");
// Absent key — check containsKey first:
if (record.containsKey("city") && !record.get("city").isNull()) {
String city = record.get("city").asString();
}---
Object Mapping
Map query results to Java records/classes directly — eliminates manual accessor calls.
// Domain record — field names match RETURN aliases (case-sensitive)
public record Person(String name, long age) {}
// Map single record
var person = driver.executableQuery("MATCH (p:Person {name: $name}) RETURN p.name AS name, p.age AS age")
.withParameters(Map.of("name", "Alice"))
.withConfig(QueryConfig.builder().withDatabase("neo4j").build())
.execute()
.records()
.stream()
.map(r -> r.get("name").asString()) // or: r.as(Person.class) — see note
.findFirst()
.orElseThrow();
// Using .as(Person.class) — maps RETURN keys to record fields by name
var person2 = driver.executableQuery("""
MATCH (p:Person {name: $name})
RETURN p.name AS name, p.age AS age
""")
.withParameters(Map.of("name", "Tom Hanks"))
.withConfig(QueryConfig.builder().withDatabase("neo4j").build())
.execute()
.records()
.stream()
.map(record -> record.get("p").as(Person.class))
.findFirst()
.orElseThrow(() -> new RuntimeException("Person not found"));Nested mapping — return a map projection and include COLLECT {} for lists:
public record Movie(String title, List<Person> actors) {}
var movieCypher = """
MATCH (movie:Movie)
LIMIT 1
RETURN movie {
.title,
actors: COLLECT {
MATCH (actor:Person)-[:ACTED_IN]->(movie)
RETURN actor
}
}
""";
var movie = driver.executableQuery(movieCypher)
.withConfig(QueryConfig.builder().withDatabase("neo4j").build())
.execute()
.records()
.stream()
.map(r -> r.get("movie").as(Movie.class))
.findFirst()
.orElseThrow();Only mapped properties defined in the record are populated — extra properties returned by Cypher are ignored.
---
Performance Patterns
Always specify database — omitting triggers home-db round-trip on every call.
Route reads to replicas — RoutingControl.READ in QueryConfig or use executeRead.
Batch writes with `UNWIND` — pass List<Map<String,Object>> (plain maps only; custom objects fail):
List<Map<String, Object>> rows = people.stream()
.map(p -> Map.<String, Object>of("name", p.name(), "age", p.age()))
.toList();
driver.executableQuery("UNWIND $items AS item MERGE (p:Person {name: item.name}) SET p.age = item.age")
.withParameters(Map.of("items", rows))
.withConfig(QueryConfig.builder().withDatabase("neo4j").build())
.execute();Allowed leaf types in parameter maps: String, Long/Integer/Short/Byte, Double/Float, Boolean, List<?>, Map<String,?>, null. Custom objects and LocalDate must be converted first.
Group writes in one transaction — one executeWrite with a loop, not one executeWrite per iteration.
Connection pool — default 100 connections. Tune if exhausted:
Config.builder()
.withMaxConnectionPoolSize(50)
.withConnectionAcquisitionTimeout(30, TimeUnit.SECONDS)
.build()---
Common Errors
| Mistake | Fix |
|---|---|
| String-interpolate Cypher params | .withParameters(Map.of(...)) always |
| Omit database name | Set in QueryConfig / SessionConfig every time |
New Driver per request | Create once at startup; share everywhere |
Share Session across threads | One session per request/thread |
Return Result from tx callback | Collect to List/Map inside callback |
Leave Result open before next tx.run() | Consume before next call |
| Side effects in managed tx callback | Move outside — callback may retry |
| Pass custom objects to UNWIND params | Convert to List<Map<String,Object>> |
asString() on graph null | .asString("default") or check .isNull() |
asString() on absent key | containsKey() before optional access |
Naked tx.rollback() in catch | Wrap in try/catch; use addSuppressed |
Assume commit() failure = no commit | Commit uncertainty — design writes idempotent |
Block inside async callback (.join()) | Chain with thenCompose |
| Skip session close in async error path | exceptionallyCompose to close then re-throw |
| One transaction per write in loop | Batch with UNWIND or group in one callback |
executeWrite for a read | Use executeRead — routes to replica |
---
References
Load on demand:
- references/async-reactive.md — full async
CompletableFuturepatterns, reactiveRxSessionwithFlux.usingWhen, deadlock avoidance - references/advanced-config.md — full
Config.builder()options, TLS, notification filtering, session-level auth, user impersonation, cross-session bookmarks, spatial types (Values.point/WGS-84/Cartesian)
Docs:
- Java Driver manual: https://neo4j.com/docs/java-manual/current/
- API reference: https://neo4j.com/docs/api/java-driver/current/
---
Checklist
- [ ] One
Driverinstance created at startup; closed on shutdown - [ ]
verifyConnectivity()called after driver creation - [ ] Database name specified in every
QueryConfig/SessionConfig - [ ] Parameters used (never string-interpolated Cypher)
- [ ]
Resultconsumed inside managed transaction callback - [ ] No side effects inside
executeRead/Writecallbacks - [ ] Sessions closed via try-with-resources
- [ ] Async sessions closed in both success and error paths (
exceptionallyCompose) - [ ]
ServiceUnavailableExceptionon commit handled as commit-uncertain - [ ]
UNWINDparams areList<Map<String,Object>>(no custom objects) - [ ]
containsKey()checked before accessing optional result columns
neo4j-driver-java-skill
Skill for writing Java (and Kotlin) code with the official Neo4j Java Driver v6.
Covers:
- Maven/Gradle dependency setup
- Driver creation,
verifyConnectivity, lifecycle management executableQuery— recommended default API- Managed transactions (
executeRead/executeWrite) with result lifecycle rules - Explicit transactions, rollback safety, commit uncertainty
- Async API (
CompletableFuture/CompletionStage) - Reactive API (Project Reactor
RxSession) - Error handling (
ServiceUnavailableException,TransientException,Neo4jException) - Data type mapping and null-safety (
asString,isNull,containsKey) - Batch writes with
UNWINDand parameter type rules - Connection pool tuning
- Causal consistency and cross-session bookmarks
Compatibility: Neo4j Java Driver v6 · Java 17+ · Kotlin 1.9+
Not covered:
- Cypher query authoring →
neo4j-cypher-skill - Driver version migrations →
neo4j-migration-skill - Spring Data Neo4j (
@Node,Neo4jRepository) →neo4j-spring-data-skill
Install:
npx skills add https://github.com/neo4j-contrib/neo4j-skills --skill neo4j-driver-java-skillOr paste into your coding assistant: https://github.com/neo4j-contrib/neo4j-skills/tree/main/neo4j-driver-java-skill
Advanced Configuration — Neo4j Java Driver
Full Config.builder() options
import org.neo4j.driver.Config;
import org.neo4j.driver.Logging;
import org.neo4j.driver.net.ServerAddress;
import org.neo4j.driver.NotificationConfig;
import org.neo4j.driver.NotificationSeverity;
import java.util.concurrent.TimeUnit;
var driver = GraphDatabase.driver(uri, auth,
Config.builder()
// Connection pool
.withMaxConnectionPoolSize(50) // default: 100
.withConnectionAcquisitionTimeout(30, TimeUnit.SECONDS) // wait for free conn
.withMaxConnectionLifetime(1, TimeUnit.HOURS)
.withConnectionLivenessCheckTimeout(30, TimeUnit.MINUTES)
// Custom resolver — useful for local dev against a cluster
.withResolver(address -> Set.of(ServerAddress.of("localhost", 7687)))
// TLS
.withEncryption()
.withTrustStrategy(Config.TrustStrategy.trustAllCertificates()) // dev ONLY
// Notification filtering — reduce noise in logs
.withNotificationConfig(NotificationConfig.defaultConfig()
.enableMinimumSeverity(NotificationSeverity.WARNING))
// Logging
.withLogging(Logging.slf4j()) // production
// .withLogging(Logging.console(Level.DEBUG)) // debug
// Record fetch size (controls Bolt batching)
.withFetchSize(1000) // default: 1000
.build());Session-level auth (multi-tenant)
Cheaper than a new Driver per tenant — reuses the connection pool:
var session = driver.session(SessionConfig.builder()
.withDatabase("tenant_db")
.withAuthToken(AuthTokens.basic("tenant-user", "pass"))
.build());User impersonation
Requires IMPERSONATE privilege on the executing user:
var session = driver.session(SessionConfig.builder()
.withDatabase("neo4j")
.withImpersonatedUser("jane")
.build());Connection pool diagnosis
| Error message | Cause | Fix |
|---|---|---|
Unable to acquire connection from the pool within configured maximum time | Pool exhausted | Increase maxConnectionPoolSize or fix session leaks |
Connection to the database terminated / ServiceUnavailableException | Network/server issue | Check server health, firewall, TLS |
| Session hangs with no error | Session leak — connection never returned | Add try-with-resources; audit all code paths |
Spatial Types
import org.neo4j.driver.Values;
// Create points — Values.point(srid, x, y) / Values.point(srid, x, y, z)
var cartesian2d = Values.point(7203, 1.23, 4.56); // Cartesian 2D
var cartesian3d = Values.point(9157, 1.23, 4.56, 7.89); // Cartesian 3D
var wgs84_2d = Values.point(4326, -0.118092, 51.509865); // WGS-84 2D (lon, lat)
var wgs84_3d = Values.point(4979, -0.0865, 51.5045, 310); // WGS-84 3D (lon, lat, height)
// Read point from result
var pt = result.records().get(0).get("location");
double x = pt.asPoint().x(); // longitude for WGS-84
double y = pt.asPoint().y(); // latitude for WGS-84
double z = pt.asPoint().z(); // height/z (NaN for 2D)
int srid = pt.asPoint().srid(); // 4326, 4979, 7203, or 9157
// Pass as parameter
driver.executableQuery("CREATE (p:Place {location: $loc})")
.withParameters(Map.of("loc", wgs84_2d))
.withConfig(QueryConfig.builder().withDatabase("neo4j").build())
.execute();
// Distance — same SRID only
driver.executableQuery("RETURN point.distance($p1, $p2) AS distance")
.withParameters(Map.of("p1", Values.point(7203, 1.0, 1.0),
"p2", Values.point(7203, 10.0, 10.0)))
.withConfig(QueryConfig.builder().withDatabase("neo4j").build())
.execute()
.records().get(0).get("distance").asDouble();SRID table: 4326 = WGS-84 2D, 4979 = WGS-84 3D, 7203 = Cartesian 2D, 9157 = Cartesian 3D.
Causal consistency — cross-session bookmarks
Within a single session: automatic. Across parallel sessions, pass bookmarks explicitly:
import org.neo4j.driver.Bookmark;
List<Bookmark> bookmarks = new ArrayList<>();
try (var sessionA = driver.session(SessionConfig.builder().withDatabase("neo4j").build())) {
sessionA.executeWriteWithoutResult(tx -> createPerson(tx, "Alice"));
bookmarks.addAll(sessionA.lastBookmarks());
}
try (var sessionB = driver.session(SessionConfig.builder().withDatabase("neo4j").build())) {
sessionB.executeWriteWithoutResult(tx -> createPerson(tx, "Bob"));
bookmarks.addAll(sessionB.lastBookmarks());
}
// sessionC waits until both Alice and Bob exist
try (var sessionC = driver.session(SessionConfig.builder()
.withDatabase("neo4j")
.withBookmarks(bookmarks)
.build())) {
sessionC.executeWriteWithoutResult(tx -> connectPeople(tx, "Alice", "Bob"));
}executableQuery shares a BookmarkManager automatically — prefer it over explicit bookmarks except for complex cross-session coordination.
Async & Reactive API — Neo4j Java Driver
Async API (driver.asyncSession())
Non-blocking via CompletableFuture / CompletionStage. Every method returns CompletionStage instead of blocking.
Session lifecycle — close in both paths
Most common mistake: leaking sessions on error.
import org.neo4j.driver.async.AsyncSession;
import org.neo4j.driver.async.ResultCursor;
CompletableFuture<List<String>> getNames(Driver driver) {
AsyncSession session = driver.asyncSession(
SessionConfig.builder().withDatabase("neo4j").build());
return session
.executeReadAsync(tx ->
tx.runAsync("MATCH (p:Person) RETURN p.name AS name")
.thenCompose(ResultCursor::listAsync)
.thenApply(records -> records.stream()
.map(r -> r.get("name").asString())
.toList())
)
// close on success
.thenCompose(names -> session.closeAsync().thenApply(v -> names))
// close on failure — use exceptionallyCompose (Java 12+) for async close
.exceptionallyCompose(e ->
session.closeAsync().thenApply(v -> { throw new RuntimeException(e); })
)
.toCompletableFuture();
}exceptionallyCompose (Java 12+): use when recovery step is itself async. exceptionally does not wait for closeAsync() to complete.
Reading records
// listAsync — collect everything into List<Record>
cursor.listAsync(r -> new Person(r.get("name").asString(), r.get("age").asInt()))
// forEachAsync — process one at a time without building List
cursor.forEachAsync(r -> System.out.println(r.get("name").asString()))Async write
CompletionStage<Void> createPerson(Driver driver, String name) {
AsyncSession session = driver.asyncSession(
SessionConfig.builder().withDatabase("neo4j").build());
return session
.executeWriteAsync(tx ->
tx.runAsync("CREATE (p:Person {name: $name})", Map.of("name", name))
.thenCompose(ResultCursor::consumeAsync)
)
.thenCompose(summary -> session.closeAsync())
.exceptionallyCompose(e ->
session.closeAsync().thenApply(v -> { throw new RuntimeException(e); })
);
}Never block inside async callback
// ❌ Deadlock risk
session.executeReadAsync(tx ->
tx.runAsync(query)
.thenApply(cursor -> cursor.listAsync().toCompletableFuture().join()) // blocks!
);
// ✅ Chain with thenCompose
session.executeReadAsync(tx ->
tx.runAsync(query).thenCompose(ResultCursor::listAsync)
);---
Reactive API (driver.rxSession())
Project Reactor / RxJava backpressure-aware streaming. Use only when downstream consumer is itself reactive.
import org.neo4j.driver.reactive.RxSession;
import reactor.core.publisher.Flux;
Flux<String> getNames(Driver driver) {
return Flux.usingWhen(
// acquire
Mono.fromSupplier(() -> driver.rxSession(
SessionConfig.builder().withDatabase("neo4j").build())),
// use
session -> Flux.from(session.executeRead(tx ->
Flux.from(tx.run("MATCH (p:Person) RETURN p.name AS name"))
.flatMap(result -> Flux.from(result.records()))
.map(r -> r.get("name").asString())
)),
// close on success
RxSession::close,
// close on error
(session, err) -> session.close(),
// close on cancel
RxSession::close
);
}Key rules:
Flux.usingWhenensures session closes in all paths (success / error / cancel).- Never subscribe inside a reactive chain — let the framework subscribe.
- Reactive is only worth the complexity when the consumer is reactive (e.g. Spring WebFlux). For Spring MVC or synchronous code, use the sync or async API.
Related skills
FAQ
Is Neo4j Driver Java Skill safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.