Overview
Graph access from Java has one dominant vendor stack and one vendor-neutral one. Neo4j's world is the Bolt binary protocol, Cypher as the query language, and Spring Data Neo4j (with the Neo4j-OGM lineage) for object mapping. Apache TinkerPop's Gremlin is the portable alternative: a traversal DSL embedded in Java that runs against JanusGraph, Amazon Neptune, and other TinkerPop-enabled stores. The prior question is whether the data earns a graph store at all — a few self-joins do not; variable-depth traversals over rich relationships do.
Key points
- Neo4j Java driver: official
neo4j-java-driverspeaks Bolt;Session/TransactionAPI, parameterized Cypher ($param— never string-concatenate), sync and reactive modes, routing awareness for clusters. - Cypher is declarative pattern matching:
MATCH (a:Person)-[:KNOWS*1..3]->(b) RETURN bexpresses a bounded variable-depth traversal in one line — the query shape that is painful in SQL. - Spring Data Neo4j (SDN):
@Node/@Relationshipmapped classes, repositories with derived finders and@QueryCypher,Neo4jTemplateunderneath; current SDN integrates the mapping layer directly (the standalone Neo4j-OGM remains for non-Spring apps — session-based, JPA-flavoured). - Quarkus and Micronaut: both integrate the official driver (Quarkiverse Neo4j extension,
micronaut-neo4j-bolt) — injection, health checks, config; object mapping is bring-your-own (OGM or hand-rolled mappers). - TinkerPop/Gremlin:
GraphTraversalSource gwith traversals likeg.V().has("name","alice").out("knows").values("name"); runs embedded or against a remote Gremlin Server. The JDBC-of-graphs: same code targets JanusGraph, Neptune, and others. - JanusGraph (open source, pluggable Cassandra/HBase backends + Elasticsearch indexing) and Neptune (managed, both Gremlin and openCypher endpoints) are the main TinkerPop targets from Java.
- Testing: Neo4j test harness (
neo4j-harness) runs an embedded in-JVM server for tests; Testcontainers' Neo4j module is the closer-to-production alternative. TinkerGraph is the in-memory TinkerPop reference graph for Gremlin unit tests. - When a graph earns its keep: variable/unbounded traversal depth, path queries
(shortest path, reachability), and relationship-heavy models queried in many directions.
Fixed shallow joins and hierarchies are fine in SQL —
WITH RECURSIVEhandles trees; graphs win when depth, direction, and relationship properties dominate the queries.
Details
The two stacks
| Neo4j stack | TinkerPop stack | |
|---|---|---|
| Protocol | Bolt | Gremlin Server (WebSocket) or embedded |
| Query language | Cypher (declarative) | Gremlin (imperative traversal DSL in Java) |
| Object mapping | Spring Data Neo4j, Neo4j-OGM | none standard — traversal results mapped by hand |
| Stores | Neo4j (also openCypher on Neptune, Memgraph) | JanusGraph, Neptune, TinkerGraph |
| Portability | one vendor (openCypher partially portable) | vendor-neutral by design |
Choosing between them
- Team writes queries analysts can read → Cypher. Need store portability or already on Neptune/JanusGraph → Gremlin. Gremlin's fluent Java API type-checks at compile time, which suits complex programmatic traversal building; Cypher strings are data.
Examples
// Neo4j driver: parameterized Cypher over Bolt
try (var session = driver.session()) {
var names = session.executeRead(tx -> tx.run(
"MATCH (p:Person {id: $id})-[:KNOWS*1..2]->(f) RETURN DISTINCT f.name AS name",
Map.of("id", personId))
.list(r -> r.get("name").asString()));
}
// Gremlin: same traversal, vendor-neutral, compile-time checked
List<Object> names = g.V().has("Person", "id", personId)
.repeat(out("knows")).times(2).emit()
.values("name").dedup().toList();
Related
- Storage access from Java — the map — parent overview by storage kind.
- Document and NoSQL stores from Java — sibling non-relational models; JanusGraph even stores into Cassandra.
- Databases and other storage systems — where graph engines sit in the storage landscape.