Chapter 13 · Application Drivers and Bolt: Sessions, Routing, Parameters, Result Streaming, and Connection Pools
Official Drivers, Bolt Protocol Concepts, URI Schemes, Authentication, TLS, and Routing Tables
Treat the official driver as a long-lived protocol, security, pooling and routing boundary—not as a disposable query helper.
Learning outcomes
AtlasMart now has Python services calling the graph. A database query can be correct in Browser and still fail in production because the client chooses the wrong URI, opens a new driver per request, negotiates TLS incorrectly, or bypasses cluster routing. This lesson treats the driver as a long-lived networking component rather than a convenience wrapper around Cypher.
Explain the responsibilities of an official Neo4j driver and the negotiated Bolt protocol.
Choose between bolt/neo4j URI
schemes and their +s/+ssc TLS
variants.
Separate driver construction, authentication, TLS verification, connectivity verification, and actual connection creation.
Distinguish direct-address connections from routing-enabled cluster/Aura connections.
Prove client/server compatibility without relying on undocumented driver internals.
The mandatory lab continues Neo4j Community
2026.07.1, database neo4j, explicit
CYPHER 25 where query-language version matters,
container atlasmart-neo4j, loopback Bolt
127.0.0.1:7687, authentication
neo4j/atlasmart-course-2026, and no
TLS on the loopback-only disposable instance. The current
official Python driver is neo4j 6.3.0 (Python
3.10–3.14), whose API supports Bolt 6.0–6.1, Bolt 5.0–5.8 and
Bolt 4.4. Neo4j 5.26.30 remains the LTS
comparison line.
This generation environment does not connect to the AtlasMart Neo4j container, so no handshake, routing-table, pool, TLS, bookmark, latency or retry output is fabricated. Each exercise gives commands and invariants to capture on your machine. True multi-member read/write routing requires an Enterprise cluster or Aura deployment; the mandatory Community path proves the driver/session/pool/stream semantics locally and labels cluster-only observations separately.
1. The driver is a protocol client with a pool
| Layer | Responsibility | Observable evidence |
|---|---|---|
| Application repository/service | Own query text, parameters, transaction boundary and API contract | request ID, business result, structured error |
| Official driver | Pool connections, negotiate Bolt, encode values, stream records, route/retry where applicable | driver debug logs, summaries, exceptions, bookmarks |
| Bolt | Binary application protocol between driver and Neo4j | handshake/protocol debug logs; default server port 7687 |
| Neo4j server | Authenticate, plan/execute Cypher, enforce constraints/transactions | query/transaction listings, server logs, result summary |
The current Python driver is 6.3.0. Its documented
protocol range includes Bolt 6.0–6.1, 5.0–5.8 and 4.4. The
current Neo4j 2026 line can negotiate Bolt 6.0 and compatible
5.x versions. Applications should use a supported official
driver and let the handshake select a mutually supported
protocol rather than hard-coding a Bolt message version.
2. URI schemes encode routing and TLS policy
| URI | Routing discovery | Encryption / certificate trust | Typical use |
|---|---|---|---|
bolt://host:7687 |
No; direct address | Unencrypted unless custom driver encryption config | Loopback lab or deliberate direct-member access |
bolt+s://host:7687 |
No | TLS; CA-signed certificate with full checks | Direct secure endpoint |
bolt+ssc://host:7687 |
No | TLS; accepts self-signed certificate | Controlled dev/private PKI scenarios; not a shortcut for public production trust |
neo4j://host:7687 |
Yes | Unencrypted unless custom encryption config | Routing-enabled self-managed cluster when TLS is separately addressed |
neo4j+s://host:7687 |
Yes | TLS + CA validation | Aura default and secure routing endpoints |
neo4j+ssc://host:7687 |
Yes | TLS + self-signed acceptance | Controlled routing deployments with intentional self-signed trust |
A neo4j:// driver obtains and refreshes routing
information; a bolt:// driver connects to the named
address and does not use routing discovery. On the mandatory
Community lab there is no multi-member cluster, so routing-role
behavior is explained and, if desired, observed separately on
Aura or an Enterprise cluster.
3. Build one driver and verify the endpoint
py -3 -m venv .venv.\.venv\Scripts\Activate.ps1python -m pip install --upgrade pippython -m pip install neo4j==6.3.0python -c "import neo4j; print(neo4j.__version__)"
from __future__ import annotationsimport osfrom neo4j import GraphDatabase, RoutingControlclass AtlasMartNeo4j: def __init__(self) -> None: self.database = os.getenv("NEO4J_DATABASE", "neo4j") self.driver = GraphDatabase.driver( os.getenv("NEO4J_URI", "bolt://127.0.0.1:7687"), auth=(os.getenv("NEO4J_USER", "neo4j"), os.getenv("NEO4J_PASSWORD", "atlasmart-course-2026")), max_connection_pool_size=20, connection_acquisition_timeout=5.0, connection_timeout=5.0, max_connection_lifetime=1800.0, max_transaction_retry_time=15.0, ) def verify(self) -> None: self.driver.verify_connectivity() def close(self) -> None: self.driver.close() def customer_orders(self, customer_id: str) -> list[dict]: records, summary, _ = self.driver.execute_query( """ CYPHER 25 MATCH (:Customer {customerId:$customerId})-[:PLACED]->(o:Order) RETURN o.orderId AS orderId, o.status AS status, o.orderedAt AS orderedAt ORDER BY o.orderedAt DESC, o.orderId """, customerId=customer_id, database_=self.database, routing_=RoutingControl.READ, ) return [r.data() for r in records]
from atlasmart_neo4j import AtlasMartNeo4jstore = AtlasMartNeo4j()try: store.verify() info = store.driver.get_server_info() print("agent:", info.agent) print("address:", info.address) print("protocol:", info.protocol_version)finally: store.close()
GraphDatabase.driver(...) configures a driver but
connection establishment is lazy.
verify_connectivity() forces an actual
compatibility/authentication/connectivity check.
get_server_info() is documented evidence for the
negotiated server/protocol; do not parse debug log strings as an
API contract.
4. Failure injection: prove that TLS/auth are separate failures
$env:NEO4J_URI='bolt://127.0.0.1:7687'$env:NEO4J_USER='neo4j'$env:NEO4J_PASSWORD='definitely-wrong'python .\check_connection.py
$env:NEO4J_URI='bolt+s://127.0.0.1:7687'$env:NEO4J_PASSWORD='atlasmart-course-2026'python .\check_connection.py
An authentication error proves the server was reachable far enough to reject credentials. A TLS/certificate error proves something different. Do not collapse every startup failure into “database unavailable,” and never log raw passwords or tokens while diagnosing it.
5. Wrong approach: one driver per HTTP request
def handle_request(customer_id): driver = GraphDatabase.driver(URI, auth=AUTH) # WRONG: expensive pool owner per request try: return driver.execute_query( "MATCH (c:Customer {customerId:$id}) RETURN c.name", id=customer_id, database_="neo4j") finally: driver.close()
This churns pools, TCP/TLS handshakes and authentication work,
and makes connection budgets unpredictable. The repair is one
maintained Driver per application/process
configuration, with short-lived sessions or
execute_query() calls borrowing from its pool.
6. Observable routing without depending on private fields
from neo4j.debug import Watcherwith Watcher("neo4j.io", "neo4j.pool"): store.verify() print(store.customer_orders("C-1001"))
The exact debug text is explicitly not API-stable, so use it only to understand handshakes, connection borrowing and routing refresh. In production telemetry, prefer your own request/transaction metadata, summaries and structured metrics over scraping driver debug messages.
Production judgment
| Decision | Evidence to collect | Risk if guessed |
|---|---|---|
| URI/routing | deployment topology and failover target | pinning a single member or sending all reads to writers |
| TLS scheme | certificate chain, hostname, rotation process | plaintext traffic or insecure certificate trust |
| driver version | server/driver compatibility matrix and upgrade tests | protocol or API incompatibility |
| driver lifetime | connection creation rate and pool metrics | churn, tail-latency spikes, exhausted sockets |
| auth rotation | credential/token lifetime and rollout test | outages during secret rotation |
Check your understanding
-
Why is
neo4j+s://not equivalent tobolt+s://? -
Is a Driver created with
GraphDatabase.driver()proof that the server is reachable? - Why keep one driver for the process?
- Can Community reproduce a true primary/secondary routing table?
- What does a negotiated Bolt version prove?
Review the answers
1. Both enable TLS with CA verification,
but neo4j+s also enables routing discovery;
bolt+s addresses one server directly.
2. No. Connections are lazy; use
verify_connectivity() or execute work.
3. The driver is thread-safe, expensive to create, and owns the reusable connection pool.
4. No. Multi-member clustered routing is an Enterprise/Aura topology concern; Community can still learn the URI and driver semantics.
5. Only that client and server found a mutually supported protocol; it does not prove application query correctness or performance.
Summary and next step
Driver construction establishes the networking policy; sessions and results establish the unit-of-work and backpressure policy. Lesson 2 follows a query from a short-lived session through a lazy result stream.
Authoritative references
- Current Neo4j versions — Current server and 5.26 LTS release snapshot.
- Neo4j Python Driver Manual — Official application-driver guide used by the mandatory lab.
- Python Driver 6.3 API — Current API, Bolt compatibility and lifecycle contract.
- Driver connection guide — Driver lifetime, connectivity checks and cluster routing.
- Advanced connection information — URI schemes, TLS, resolver and connection configuration.
- Transactions with the Python driver — Session/transaction lifecycle, managed retries and result streaming.
- Python driver performance recommendations — Lazy streaming, fetch size and read routing guidance.
- Bolt compatibility matrix — Neo4j DBMS and negotiated Bolt protocol versions.
- Neo4j Drivers — Official vs community driver list.
- Python package 6.3.0 — Release pin used by the local lab.