neo4j-driver-java-skill について
Java または Kotlin アプリケーションから Neo4j に接続する開発者向けのスキルガイドです。ドライバー設定、同期・非同期アクセス、トランザクション、結果マッピング、エラー対応、性能改善を扱います。
主な機能
- 01Neo4j Java Driver の設定
- 02クエリ、トランザクション、アクセス方式の選択
- 03結果、型、オブジェクトマッピングの処理
- 04接続とトランザクションのエラー調査
- 05バッチ書き込みとプール調整
活用シーン
- 01Java または Kotlin プロジェクトから Neo4j へ接続
- 02大きなクエリ結果やストリーム読み込みの処理
- 03複数メソッドにまたがる明示的トランザクションの管理
- 04プールや結果処理の問題の調査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ neo4j-contrib/ neo4j-skills --skill neo4j-driver-java-skillインストール後、Claude Code で/neo4j-driver-java-skillを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
README.mdreferences/advanced-config.mdasync-reactive.mdproperty-encryption.md詳細
使用する場面
- Neo4j(Aura またはセルフマネージド)に接続する Java/Kotlin コード
- Maven/Gradle プロジェクトでのドライバー、セッション、トランザクションの設定
- 結果の処理、エラーからの復旧、接続プールの問題のデバッグ
- Neo4j への非同期(
CompletableFuture)またはリアクティブ(Project Reactor/RxJava)アクセス
使用しない場面
- Cypher クエリの作成/最適化 →
neo4j-cypher-skill - ドライバーのバージョンアップグレード →
neo4j-migration-skill - Spring Data Neo4j(
@Node、@Relationship、Neo4jRepository)→neo4j-spring-data-skill
依存関係
Maven
<dependency>
<groupId>org.neo4j.driver</groupId>
<artifactId>neo4j-java-driver</artifactId>
<version>6.3.0</version>
</dependency>
Gradle
implementation 'org.neo4j.driver:neo4j-java-driver:6.3.0'
最新バージョンはこちらで確認してください:https://central.sonatype.com/artifact/org.neo4j.driver/neo4j-java-driver
6.2.0 [2026-06]:Neo4j の UUID 型と Bolt 6.1 のサポート、結果サマリーの QueryProfile に対応しています。
6.2.1 [2026-08]:UUID 値に対する Value#asObject() の問題を修正しています。UUID プロパティを汎用的に読み取る場合に必要です。Neo4j 2026.08 サーバーには 6.2.1 が、2026.07 には 6.2.0 が同梱されています。
6.3.0 [2026-09]:プロパティ暗号化のプレビュー版。プロパティ値のクライアント側エンベロープ暗号化(driver.propertyEncryption())→ references/property-encryption.md。
サーバー側の UUID 型と uuid() コンストラクターは Neo4j 2026.08 で提供されます。これらのサーバーに接続する場合は、ドライバー 6.2.1 以降を使用してください。
環境変数
接続設定には標準的な方法を使い、認証情報をハードコードしないでください。
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 では、@Value("${spring.neo4j.uri}") または application.properties を使って注入します。
spring.neo4j.uri=neo4j+s://xxx.databases.neo4j.io
spring.neo4j.authentication.username=neo4j
spring.neo4j.authentication.password=secret
ドライバーのライフサイクル
アプリケーションごとに Driver を 1 つ使用します。スレッドセーフですが、作成コストが高いためです。AutoCloseable を実装するか、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 スキーム:
| URI | 用途 |
|---|---|
neo4j://localhost | 暗号化なし、クラスタールーティング |
neo4j+s://xxx.databases.neo4j.io | TLS + クラスタールーティング(Aura) |
bolt://localhost:7687 | 暗号化なし、単一インスタンス |
bolt+s://localhost:7687 | TLS、単一インスタンス |
認証オプション:AuthTokens.basic(u,p) · AuthTokens.bearer(token) · AuthTokens.kerberos(b64) · AuthTokens.none()
適切な API の選択
| API | 使用場面 | 自動再試行 | ストリーミング |
|---|---|---|---|
driver.executableQuery() | ほとんどのクエリでのデフォルト | ✅ | ❌ イーガー |
session.executeRead/Write() | 大量の結果、コールバック制御 | ✅ | ✅ |
session.beginTransaction() | 複数メソッド、外部との連携 | ❌ | ✅ |
session.run() | 自己管理型クエリ(CALL IN TRANSACTIONS) | ⚠️ ワンショット [6.1+] | ✅ |
driver.asyncSession() | ノンブロッキングの CompletableFuture | ✅ | ✅ |
driver.rxSession() | Reactor/RxJava のバックプレッシャー | ✅ | ✅ |
CALL { … } IN TRANSACTIONS と USING PERIODIC COMMIT はトランザクションを自己管理します。これらには session.run() だけを使用してください。executableQuery と executeRead/Write はこれらのクエリで失敗します。
session.run() の再試行 [6.1+]:冪等性のあるエラーに対して、即時再試行を 1 回だけ行います(デフォルトで有効)。ドライバーまたはセッションごとに無効化できます。
// 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 — デフォルト
// 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();
Cypher を文字列補間してはいけません。常に .withParameters(Map.of(...)) を使用してください。
管理対象トランザクション(executeRead/executeWrite)
セッションはスレッドセーフではありません。リクエスト/スレッドごとに 1 つ使用し、必ず閉じてください。
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 は、開いているトランザクションに紐づいた遅延カーソルです。コールバックが返るとトランザクションが閉じるため、その後に読み取ると 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());
コールバックのルール
- 次の
tx.run()を呼び出す前に、各Resultを消費してください。複数のカーソルを開いたままにすると、動作は未定義です。 - 副作用(HTTP、メール、メトリクスの増分)を発生させないでください。コールバックは一時的なエラーで再試行される場合があります。
- 再試行に安全な書き込みには
CREATEではなく、冪等なMERGEを使用してください。 executeRead→ レプリカ、executeWrite→ リーダー。
TransactionConfig — タイムアウトとメタデータ
var config = TransactionConfig.builder()
.withTimeout(Duration.ofSeconds(5))
.withMetadata(Map.of("app", "myService", "user", userId)) // visible in SHOW TRANSACTIONS
.build();
session.executeRead(tx -> { /* ... */ }, config);
明示的なトランザクション
複数のメソッドにまたがる処理や、外部との連携が必要な場合に使用します。自動再試行されません。
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() はネットワーク呼び出しです。独自の try/catch で囲み、元の例外が失われないように addSuppressed を使用してください。
コミットの不確実性:tx.commit() が ServiceUnavailableException をスローした場合、コミットが成功したかどうかは不明です。再試行しても安全なように、書き込みは冪等に設計してください(MERGE +一意制約)。
明示的なトランザクションと管理対象トランザクションの選択:
- 自動再試行が必要 →
executeRead/executeWrite - 複数のメソッドにまたがる処理 → 明示的なトランザクション(
txをパラメーターとして渡す) - 外部 I/O との連携 → 明示的なトランザクション(I/O の成功後にのみコミット)
エラー処理
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
}
管理対象トランザクションは TransientException を自動再試行するため、catch は不要です。
データ型と値の抽出
| Cypher の型 | Java アクセサー |
|---|---|
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() |
UUID | value.asUUID() → java.util.UUID [6.2+, Neo4j 2026.08+] |
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 安全性 — 明確に異なる 2 つのケース
| 状況 | record.get(key) | .asString() |
|---|---|---|
| キーが存在し、値が null ではない | 値 | 文字列を返す |
| キーが存在し、値がグラフの null | .isNull() = true となる Value | Uncoercible をスロー |
| キーが存在しない(タイプミス/射影されていない) | Value.NULL センチネル | 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();
}
オブジェクトマッピング
クエリ結果を Java のレコード/クラスに直接マッピングすれば、アクセサーを手動で呼び出す必要がなくなります。
// 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"));
ネストされたマッピング — マップ射影を返し、リストには COLLECT {} を含めます。
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();
レコードで定義されているマッピング対象のプロパティのみが設定されます。Cypher が返す追加のプロパティは無視されます。
パフォーマンスのパターン
データベースを必ず指定してください — 省略すると、呼び出しのたびにホーム DB へのラウンドトリップが発生します。
読み取りをレプリカにルーティングします — QueryConfig で RoutingControl.READ を指定するか、executeRead を使用します。
UNWIND で書き込みをバッチ処理します — List<Map<String,Object>> を渡します(プレーンなマップのみ使用できます。カスタムオブジェクトは失敗します)。
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();
パラメーターのマップで許可されるリーフ型:String、Long/Integer/Short/Byte、Double/Float、Boolean、List<?>、Map<String,?>、null。カスタムオブジェクトと LocalDate は、事前に変換する必要があります。
書き込みを 1 つのトランザクションにまとめます — 反復ごとに executeWrite を呼び出すのではなく、ループを含む executeWrite を 1 回実行します。
接続プール — デフォルトは 100 接続です。枯渇する場合は調整してください:
Config.builder()
.withMaxConnectionPoolSize(50)
.withConnectionAcquisitionTimeout(30, TimeUnit.SECONDS)
.build()
よくあるエラー
| 誤り | 修正方法 |
|---|---|
| Cypher のパラメーターを文字列補間する | 常に .withParameters(Map.of(...)) を使用します |
| データベース名を省略する | 毎回 QueryConfig / SessionConfig に設定します |
リクエストごとに新しい Driver を作成する | 起動時に一度だけ作成し、全体で共有します |
Session をスレッド間で共有する | リクエスト/スレッドごとにセッションを1つ使用します |
トランザクションコールバックから Result を返す | コールバック内で List / Map に収集します |
次の tx.run() の前に Result を開いたままにする | 次の呼び出しの前に消費します |
| 管理対象トランザクションコールバック内で副作用を発生させる | 外部に移します。コールバックは再試行される場合があります |
| カスタムオブジェクトを UNWIND パラメーターに渡す | List<Map<String,Object>> に変換します |
グラフの null に asString() を使う | .asString("default") を使うか、.isNull() を確認します |
存在しないキーに asString() を使う | 任意アクセスの前に containsKey() を確認します |
catch 内でそのまま tx.rollback() を呼び出す | try/catch で囲み、addSuppressed を使用します |
commit() の失敗はコミットされていないことを意味すると想定する | コミットの成否は不確実なため、書き込みを冪等に設計します |
非同期コールバック内でブロックする(.join()) | thenCompose でチェーンします |
| 非同期エラー経路でセッションのクローズを省略する | exceptionallyCompose でクローズしてから再スローします |
| ループ内で書き込みごとにトランザクションを作成する | UNWIND でバッチ処理するか、1つのコールバックにまとめます |
読み取りに executeWrite を使う | レプリカにルーティングする executeRead を使用します |
参考資料
必要に応じて読み込んでください:
- references/async-reactive.md — 非同期
CompletableFutureの詳細なパターン、Flux.usingWhenを使ったリアクティブなRxSession、デッドロックの回避 - references/property-encryption.md — プロパティ暗号化のプレビュー [6.3.0+]:エンベローププロファイル、鍵カプセル化サービス(ローカル、AWS KMS、Azure Key Vault、GCP KMS)、鍵マネージャー、AAD を使用した暗号化/復号
- references/advanced-config.md —
Config.builder()の全オプション、TLS、通知フィルタリング、セッションレベルの認証、ユーザーの偽装、セッション間ブックマーク、空間型(Values.point/WGS-84/Cartesian)
ドキュメント:
- Java Driver マニュアル:https://neo4j.com/docs/java-manual/current/
- API リファレンス:https://neo4j.com/docs/api/java-driver/current/
チェックリスト
- 起動時に
Driverインスタンスを1つ作成し、シャットダウン時にクローズする - ドライバー作成後に
verifyConnectivity()を呼び出す - すべての
QueryConfig/SessionConfigにデータベース名を指定する - パラメーターを使用する(Cypher を文字列補間しない)
- 管理対象トランザクションコールバック内で
Resultを消費する -
executeRead/Writeコールバック内で副作用を発生させない - try-with-resources でセッションをクローズする
- 成功時とエラー時の両方の経路で非同期セッションをクローズする(
exceptionallyCompose) - コミット時の
ServiceUnavailableExceptionをコミットの成否が不確実なエラーとして処理する -
UNWINDパラメーターをList<Map<String,Object>>にする(カスタムオブジェクトは使用しない) - 任意の結果列にアクセスする前に
containsKey()を確認する