# neo4j 'OutOfMemoryError: Java heap space' on a traversal: how to bound the query

## TL;DR
Bound the traversal before you grow the heap: PROFILE the query to find the expanding pattern (usually an unbounded variable-length path or a cartesian product), then add hop limits, direction, and relationship types. Raising the heap without bounding the query just moves the OOM to a bigger number.

## The error

```text
OutOfMemoryError: Java heap space
```

## Steps

1. PROFILE the query and look at which operator explodes - rows multiplying by orders of magnitude between stages:

```
PROFILE [your traversal query]
```

   Expected: one Expand or CartesianProduct step where row counts jump from thousands to millions.

2. Find the unbounded pattern. The classic killers are variable-length paths with no upper bound and no relationship type:

```
// dangerous: every path of any length, any relationship
MATCH p = (a)-[*]-(b) RETURN p
```

   Expected: you can point at the `[*]` (or the missing direction/type) as the expansion source.

3. Bound it: cap the hops and name the relationship types (add a direction to the pattern in your query editor when the traversal has a natural direction):

```
MATCH p = (a)-[:KNOWS|WORKS_WITH*1..3]-(b)
RETURN p LIMIT 100
```

   Expected: PROFILE now shows bounded row counts; the query returns instead of OOMing.

4. For traversals that must touch many nodes (bulk updates, large reads), page through them in batches instead of materializing everything:

```
CALL apoc.periodic.iterate(
  'MATCH (n:Label) WHERE [condition] RETURN n',
  'DETACH DELETE n',
  {batchSize: 10000}
)
```

   Expected: steady progress with flat memory instead of one giant transaction blowing the heap.

5. Only after the query is bounded, size the heap sanely: heap for query state, page cache for the graph, and never heap plus page cache above physical RAM minus OS headroom.

   Expected: the bounded query runs comfortably; headroom remains for concurrent load.

## Use this when

- Neo4j throws `OutOfMemoryError: Java heap space` on a traversal or path query.
- A query works on a small graph and dies as the graph grows.
- PROFILE shows row-count explosions in Expand steps.

## Not for this skill when

- The OOM happens at startup or during import (that is heap/page-cache sizing or bulk-import config, not a traversal).
- The error is a native memory or page-cache issue rather than Java heap.
- The query is already bounded and still OOMs on a huge result set (then stream or paginate the results; the result materialization is the cost).

## Variant phrasings

### neo4j java heap space on match query
Same error; the PROFILE-and-bound workflow applies.

### cypher variable length path out of memory
The `[*1..3]` bound plus relationship types from step 3.

### neo4j traversal too much memory
Check for cartesian products between disconnected patterns first.

## Why it happens
An unbounded traversal enumerates paths, and paths grow exponentially with hops: each extra hop multiplies the intermediate result set by the average node degree. Neo4j materializes those intermediate rows in heap memory, so a 6-hop unbounded traversal on a dense graph can generate billions of rows from a small database. The heap fills long before the query finishes, and the JVM dies.

## Edge cases

- `shortestPath` with no upper bound still explores widely on dense graphs; cap it and add relationship types.
- OPTIONAL MATCH after an expanding pattern multiplies rows even when nothing matches; bound the pattern before the OPTIONAL.
- apoc.periodic.iterate batches the writes but the driving query must still be bounded; an unbounded driver OOMs inside the batcher.
- Do not set the heap to nearly all RAM; the OS page cache and off-heap needs starve, and the database gets slower, not faster.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_7OFcM7XibkgICnmt6q55ng
