main is the active development branch — base feature branches on it and
target PRs at it. 4.4 is a diverged, older release line (it predates the
JRuby/shared split and the auth-manager work); don't branch off it.
- Bolt Protocol: https://neo4j.com/docs/bolt/current/
- PackStream: https://neo4j.com/docs/bolt/current/packstream/
- Java (reference impl): https://github.com/neo4j/neo4j-java-driver and https://github.com/neo4j/bolt-connection-java
- Python: https://github.com/neo4j/neo4j-python-driver
- JavaScript: https://github.com/neo4j/neo4j-javascript-driver
- Go: https://github.com/neo4j/neo4j-go-driver
When in doubt, check the Java driver - it's the most comprehensive reference implementation.
Two implementations behind one public API: the MRI impl is a pure-Ruby implementation of the Neo4j Bolt protocol (no Java dependency); the JRuby impl wraps the official Java driver. When in doubt about driver semantics, the Java driver is the reference: https://github.com/neo4j/neo4j-java-driver
See DEVELOPMENT.md for dev-loop commands and DECISIONS.md for the
dated log of architectural choices.
Dev tree is split into lib/{shared,mri,jruby}/. The published gem is
flattened to lib/ via a staged build (Pattern 1 — see JRUBY.md).
lib/shared/neo4j/driver.rb Gem entry (Zeitwerk setup; pushes shared + impl roots)
lib/mri/neo4j/driver/ MRI implementation
bolt/ Bolt protocol: connection, messages
packstream/ Binary serialization: packer/unpacker
types/ Neo4j types (Node, Relationship, Path, temporal, Point, Duration)
exceptions/ Exception hierarchy, one class per file
session.rb Session + auto-commit
transaction.rb Explicit transactions
result.rb Streaming result
record.rb / summary.rb
lib/jruby/neo4j/driver/ JRuby implementation: thin wrapper over the
Java driver (org.neo4j.driver jars). `ext/`
mixins are prepended onto the Java classes
for Ruby<->Java type/exception conversion.
- Node labels, relationship types, field keys, and property keys → symbols (converted at hydration).
- All keys are stored as symbols; retrieval by string is tolerated (
Record#[]/Entity#[]coerce withto_sym). session.run(query, parameters = {}, config = {})— explicit split. Same key allowed in both hashes.- Timeouts are seconds (or
ActiveSupport::Duration); converted to ms for the Bolt wire internally. - Bookmarks are replaced on each successful commit, not accumulated.
session.last_bookmarksreturns a Set of 0 or 1. Rollback, failure, and auto-commit queries do not update them.
session.run— auto-commit, no BEGIN.session.execute_read/write { |tx| … }— managed. Auto-commits on clean exit; retries transient failures with exponential backoff (1s, 2s, 4s…) up tomax_transaction_retry_time.session.begin_transaction { |tx| … }— explicit. Default-rollback on clean exit; user must calltx.commit.
Signatures 0x46 (DateTime with offset) and 0x66 (DateTime with zone name) encode
epoch_seconds as wall-clock time treated as if it were UTC, not the true
UTC instant. Pack adds utc_offset; hydrate subtracts it. For 0x66 specifically,
use tz.tzinfo.local_to_utc(wall_clock) so the zone's actual offset at that
instant is applied — 2 * tz.utc_offset only happens to work in summer because
standard offset doubled equals the DST offset.
- Trust callers. No defensive
.dup,.freeze, or type guards unless the task requires it. - Ruby 3.4+ idioms: hash value omission (
metadata:), method references (&Bookmark.method(:new)),itblock parameter (hash.transform_values { it.foo }),&.then { ... }pipelines over guard-and-statement,Array(),.compactover nil-skipping conditionals. - Polymorphism over
is_a?/case/whenon type. Tell, don't ask. - Explicit over clever. Separate parameters from config rather than extracting from merged kwargs.
- Remove duplication at the second occurrence. An abstract class earns its keep only through reuse.
- Zeitwerk one-class-per-file. Namespace modules are autovivified from directory names — do not create
<namespace>.rbstubs that just declaremodule Foo; end. - All stdlib/gem
requires live inlib/neo4j/driver.rb. Never scatter them in internal files. - Error messages: helpful and contextual (
"Transaction is already closed", not"Invalid state"). Map to Neo4j error codes where applicable.
TEST_NEO4J_URL=bolt://localhost:7687
TEST_NEO4J_USER=neo4j
TEST_NEO4J_PASS=password
bundle exec rspecspec/shared/integration/— end-to-end against a running Neo4j instance (run on both impls).spec/shared/neo4j/driver/— unit tests of the public API (run on both impls).spec/mri/andspec/jruby/— impl-specific tests.
https://github.com/neo4j-drivers/testkit is the shared integration/conformance test suite for Neo4j drivers. The testkit-backend/ directory contains the Ruby backend that testkit's Python test runner talks to over a TCP socket using a line-delimited JSON protocol. The testkit/ directory one level up holds the Python orchestration scripts that testkit's Docker runner calls.