Every large language model application eventually hits the same wall: text alone cannot answer βhow is entity A related to entity B through three intermediaries?β Vector search gives you semantic neighbors, but relationships β who funded whom, which user reviewed which order shipped to which address β live in edges, not embeddings. That is why knowledge graphs have become a standard backbone for RAG systems: an LLM extracts entities and relationships from raw text, writes them as graph nodes and edges, and retrieval then walks those edges across multiple hops with sub-second latency. Fraud rings, recommendation paths, bill-of-materials explosions, and drug-interaction chains all collapse into the same shape problem.
Neo4j is the most widely deployed native graph database, and its GitHub repository hosts the full Community Edition source in Java. Native is the operative word: unlike a relational engine that would answer a three-hop traversal with repeated joins, Neo4j stores relationships as physical pointers between records, so each hop is one dereference instead of a scan. For multi-hop queries this is the difference between milliseconds and minutes, and it is exactly the access pattern GraphRAG-style retrievers generate in volume. If your stack already leans on vector stores like Milvus or Qdrant for semantic recall, a graph store complements them by answering the structural questions embeddings cannot.
This post is a source tour of that engine. We compiled two architecture diagrams from the current 2026.10 branch and verified every class and line number below directly in the tree. The first diagram shows the end-to-end life of a query; the second zooms into the classes that do the work.
The request path at a glance: a Bolt driver connection is parsed, planned, and executed against the kernel transaction API, which buffers changes in TxState and commits them through the record storage engine, the page cache, and the write-ahead log.
Reading the overview from left to right:
- BoltProtocol, L66 is the interface every wire-protocol version implements; handshake classes like ModernProtocolHandshakeHandler, L35 negotiate which of the versioned handlers (v40 through v61) will decode the session.
- StateMachineImpl, L41 is the connection state machine that walks a session through READY, STREAMING, and transaction states, rejecting out-of-order messages as protocol errors.
- Cypher5AstParser, L39 turns query text into an AST; CachingPreParser, L79 normalizes queries first and serves repeated statements from a cache.
- IDPQueryGraphSolver, L144 is the cost-based planner core, using iterative dynamic programming over join orders, with CardinalityCostModel, L160 scoring each candidate.
- CypherRuntime, L55 binds a plan to an execution engine; InterpretedRuntime, L72 compiles the plan into a tree of operators such as AllNodesScanPipe, L28 and ProduceResultsPipe, L31.
- KernelTransactionImplementation, L210 is the transaction workhorse: its
commitat L1195 funnels intocommitTransactionat L1325, which hands the buffered TxState, L110 to the commit process. - BatchingTransactionAppender, L35 serializes the command batch and its
appendToLogat L107 writes it through EnvelopedWALService, L35, the enveloped write-ahead log. - RecordStorageEngine, L146 applies the commands via
applyat L511; NeoStores, L60 owns the individual stores, all reading and writing through the PageCache, L47.
The detail view: each block is a real class in the repository β click any node in the source diagram to open the file at the exact line cited here.
Bolt: The Wire Protocol Under Every Driver
Every official driver β Python, JavaScript, Java, Go β speaks Bolt, a compact binary protocol over a single TCP connection. The server side lives in community/bolt. Negotiation is layered: the handshake handler reads the clientβs proposed versions, the registry resolves them to a concrete BoltProtocol implementation, and that implementation constructs the state machine and message processors for the session. The design pays off for AI agents that hold long sessions: one multiplexed connection carries many queries, and the FSM keeps a slow streaming result from blocking a new query.
Cypher: Parsing and the IDP Cost Planner
Cypher is Neo4jβs declarative graph query language β pattern matching rather than table joins. Compilation happens in stages: the pre-parser normalizes and caches, the ANTLR-based parser builds the AST, semantic analysis decorates it, and the logical planner converts patterns into plan operators. The interesting part is join ordering: the IDP solver builds larger plan fragments from the cheapest combinations of smaller ones, guided by the cardinality model that estimates how many rows each pattern will match. Graph statistics β labels, relationship types, selectivity β feed those estimates, which is why freshly loaded graphs sometimes plan poorly until statistics accumulate. The interpreted runtime then lowers the plan into a pipe tree, each pipe pulling rows from its source, with cursors into the kernel underneath.
The Kernel: TxState and the Commit Path
The kernel is the ACID heart. A transaction is a KernelTransactionImplementation instance with a TxState object that buffers every mutation β reads see committed data plus their own writes through an overlay. On commit, three things happen in a strict order: changes are converted into a storage-agnostic command batch, that batch is appended to the write-ahead log, and only then is it applied to the stores. The CommandBatch interface at L30 is the contract between kernel and storage engine, which is why the same kernel can drive different storage backends. The WAL itself was recently carved into its own community/wal module; the enveloped format wraps each entry with a header and checksum so torn writes at the tail of a crash are detected and truncated cleanly at recovery.
Record Storage: Index-Free Adjacency on Disk
The record storage engine implements the kernel contract with fixed-size records. NodeStore keeps one record per node β flags, first relationship pointer, first property pointer. RelationshipStore is the clever bit: each relationship record stores both endpoints plus prev/next pointers per direction, so a nodeβs relationships form doubly linked lists threaded through the relationship file. Traversing from node A to node B means reading Aβs record, chasing one pointer to a relationship record, and dereferencing the other endpoint β no index lookup, no join. Properties live in PropertyStore blocks with small values inlined and large ones spilling into DynamicRecord chains. Every store file is mapped through the Muninn page cache, a fork of the design used by the engine itself for the StorageReader, L42 cursors the runtime consumes, and the record format β standard or page-aligned β is selected at store creation by RecordFormatSelector, L62. Compared to the B-tree workhorses behind PostgreSQL or the single-file simplicity of SQLite, this is a storage layout only a graph can love: pointer chasing instead of key lookups. Secondary indexes β native GBPTree range indexes from RangeIndexProvider, L104 and Lucene-backed text indexes β accelerate entry points, but the traversal itself stays pointer-driven.
From Install to Your First Model
The repository builds like most large Java projects, and the README keeps it honest about the prerequisites. You need Apache Maven 3.9.11 or newer plus a supported JDK β the README suggests openjdk-21-jdk on Ubuntu β and Maven itself needs extra heap: export MAVEN_OPTS="-Xmx2048m". With those in place, a plain mvn clean install -T1C builds the individual jars with tests; if you only want the artifacts, the README recommends mvn clean install -DskipTests -T1C. After the build completes, cd into packaging/standalone/target, extract the distribution you want, and run the server start script. From a knowledge-graph angle, the fastest way to a first model is to feed extracted triples β subject, predicate, object β through the official Python driver with MERGE statements so re-runs deduplicate nodes and edges, then attach your embeddings as node properties where you want hybrid retrieval. Give the page cache a meaningful share of RAM in neo4j.conf before loading real data; a graph that fits in cache is a different animal from one that thrashes.
Honest Limits
- Community versus Enterprise. The README is explicit: Community Edition is GPLv3, while Enterprise includes closed-source components that are not in this repository. Practical consequences: online backup, hot resizing, and the clustering and causal-read replication stack are Enterprise features, so this repoβs Community server is effectively single-node.
- Repository layout. The public tree contains the
community/modules,annotations, andpackagingβ there is noenterprise/source here, so any tour of replication or multi-database cluster internals will hit a closed door in the public code. - JVM memory tuning. This is a Java engine with two memory pools to budget β heap for execution and transaction state, page cache for the store files. The build itself asks for 2 GB of Maven heap, and undersized production heaps show up as GC pauses, not clean errors.
- Cypher ecosystem maturity. Cypher is expressive for patterns, but the broader tooling β BI connectors, ORMs, migration frameworks β is thinner than the SQL ecosystem around PostgreSQL; property-graph extensions exist for relational engines, but they do not match a native engineβs traversal performance.
- GPLv3 obligations. GPLv3 is viral in a way MIT and Apache licenses are not; if you distribute a product containing Community Edition, the license terms follow it.
Where does Neo4j sit in a modern retrieval stack? Treat it as the structural half: the entity-relationship skeleton your extraction step writes to, queried with Cypher to assemble multi-hop context for the model before generation. Vector stores answer the semantic half β neighbors in embedding space, with the schema-free flexibility that makes them quick to bootstrap β and the two compose into hybrid retrieval that neither approach provides alone. The storage philosophies are also worth contrasting: where log-structured engines continuously rewrite the disk layout to make writes fast, Neo4jβs fixed records and stable pointers make reads and traversals predictable, and that choice is visible everywhere in the source you just toured β two very different answers to the same question of what a database engine should spend its I/O budget on. Enjoyed this post? Never miss out on future posts by following us