Chapter 12 · APOC, Procedures, Functions, Triggers-Like Workflows, and Extending Cypher
Collections, Maps, Text, Date, Conversion, Path Expansion, and Utility Procedures
Compare native Cypher with selected APOC Core collection, map, text and path helpers, keeping row cardinality and deprecation risk observable.
Learning outcomes
AtlasMart receives product attributes in inconsistent maps and needs a bounded “related supply-chain entities” view. Both requirements can be solved several ways. The engineering question is not whether APOC has a function for them; it is whether APOC adds expressive value without hiding cardinality, compatibility or maintenance costs.
Compare native Cypher collection/map/text/date expressions with APOC Core equivalents.
Use APOC functions inside row expressions and procedures through CALL/YIELD without confusing their cardinality.
Apply apoc.path.expandConfig with explicit
bounds and filters.
Recognize APOC utilities deprecated in Cypher 25 when native language features supersede them.
Document why a chosen helper is less coupled or more testable than its alternatives.
The mandatory lab continues Neo4j Community
2026.07.1, database neo4j, explicit
CYPHER 25 for version-sensitive examples,
container atlasmart-neo4j, authentication
enabled, loopback HTTP/Bolt endpoints, and the AtlasMart
identifiers/model built in Chapters 01–11. Neo4j
5.26.30 remains the LTS comparison line. This
chapter adds APOC Core 2026.07.1 as an optional
extension; APOC Extended is never required.
This generation environment does not run the Neo4j container, so no plugin load, procedure list, filesystem read/write, HTTP request, trigger execution or custom-JAR output is invented. The lab tells you exactly what to inspect. Community Edition lacks Enterprise RBAC/load privileges, so the free path emphasizes configuration minimization, loopback-only access, file/network denial by default, and application-level authorization boundaries.
1. Prefer the language first; use APOC for a capability gap
| Requirement | Pure Cypher first | APOC Core candidate | Decision |
|---|---|---|---|
| Deduplicate list |
UNWIND + DISTINCT +
collect
|
apoc.coll.toSet() when its ordering contract
is acceptable
|
Cypher is explicit; APOC can be concise |
| Merge two maps | Map projection / explicit keys | apoc.map.merge() |
APOC is useful for truly dynamic maps |
| Normalize product search key | Native string functions/regex where sufficient | apoc.text.clean() |
Choose based on exact normalization contract |
| Date/time arithmetic |
Native date()/datetime()/duration()
|
Some legacy APOC date helpers | Prefer native temporal types in new Cypher 25 work |
| Fine-grained path expansion | Quantified path patterns + predicates | apoc.path.expandConfig() |
APOC when configurable relationship/label filters or uniqueness modes materially simplify the traversal |
APOC 2025.07+ evolves for Cypher 25. Some older conveniences are deprecated as Cypher gains equivalent language features. A stable codebase treats deprecation reports as migration input rather than freezing historical recipes.
2. Functions preserve the row; procedures can multiply it
CYPHER 25WITH ['Cameras','Store IoT','Cameras'] AS raw, {source:'erp', priority:1} AS leftMap, {priority:2, verified:true} AS rightMapRETURN apoc.coll.toSet(raw) AS uniqueCategories, apoc.map.merge(leftMap,rightMap) AS merged, apoc.text.clean(' Trail-Camera 1001 ') AS normalized;
A function produces a value in the current row. By contrast, a path procedure can emit many rows per input node, so its cardinality belongs in the query design.
CYPHER 25MATCH (s:Supplier {supplierId:'SUP-1001'})CALL apoc.path.expandConfig(s, { relationshipFilter:'SUPPLIES>|SUPPLIED_BY<|IN_CATEGORY>|STOCKED_AT>', minLevel:1, maxLevel:3, bfs:true, uniqueness:'RELATIONSHIP_PATH', limit:25}) YIELD pathRETURN length(path) AS hops, [n IN nodes(path) | coalesce(n.productId,n.categoryId,n.storeId,n.supplierId)] AS idsORDER BY hops, ids;
apoc.path.expandConfig can be more configurable
than a plain pattern, but it does not abolish graph fan-out.
maxLevel, relationship/label filters, uniqueness
mode and result limits are correctness/performance inputs—not
afterthoughts.
3. Same result, three implementation locations
CYPHER 25MATCH (p:Product {productId:$id})RETURN p{.productId,.name,.category,.price, displayName: toUpper(p.name)} AS product;
CYPHER 25MATCH (p:Product {productId:$id})WITH p, p{.productId,.name,.category,.price} AS baseRETURN apoc.map.merge(base,{displayName:toUpper(p.name)}) AS product;
record = session.run( "MATCH (p:Product {productId:$id}) RETURN p{.*} AS product", id="P-1001",).single()product = dict(record["product"])product["displayName"] = product["name"].upper()
All three can be correct. Native Cypher keeps logic near the query and avoids plugin coupling; APOC is useful for dynamic server-side transformations; application code can be easier to unit-test and keeps presentation logic out of the database. Measure payload size and round trips before assuming one is faster.
4. Edge cases that change the answer
| Edge case | Failure if ignored | Repair |
|---|---|---|
List contains null |
Predicate/function behavior can propagate null unexpectedly | Specify null filtering before deduplication/transformation |
| Dynamic map overwrites stable ID | Merge precedence silently changes identity field | Keep identity outside mutable overlay or assert key set |
| Text normalization is locale/domain-sensitive | “Clean” may collapse distinct business values | Define canonicalization contract in application/domain tests |
| Path includes cycles/high-degree hubs | Result rows/memory grow sharply | Bound depth, filter relationship types/labels and test dense cases |
| APOC helper deprecated in Cypher 25 | Upgrade emits warnings or future removal risk | Use current native replacement and regression-test result equivalence |
5. Deliberately wrong: APOC path expansion as a universal shortest path engine
apoc.path.expandConfig controls traversal
expansion; it is not automatically equivalent to Cypher shortest
selectors or a weighted graph algorithm. If AtlasMart asks for
minimum freight cost, a “first path returned by BFS” is not a
weighted shortest-path guarantee. Choose transactional Cypher
for bounded pattern questions and GDS/application algorithms for
algorithmic weighted routing.
6. Lab: compare outputs, not syntax length
CYPHER 25MERGE (c:Customer {customerId:'C-1001'})SET c.name='Mina Rahimi'MERGE (p1:Product {productId:'P-1001'})SET p1.name='Trail Camera', p1.category='Cameras', p1.price=129.90MERGE (p2:Product {productId:'P-2001'})SET p2.name='Smart Shelf Sensor', p2.category='Store IoT', p2.price=79.50MERGE (o:Order {orderId:'O-5001'})SET o.status='PAID', o.orderedAt=datetime('2026-09-08T16:30:00Z')MERGE (c)-[:PLACED]->(o)MERGE (o)-[:CONTAINS {quantity:1}]->(p1)MERGE (o)-[:CONTAINS {quantity:2}]->(p2);
CYPHER 25CALL apoc.help('map.merge') YIELD name,type,text RETURN name,type,text;CALL apoc.help('path.expandConfig') YIELD name,type,text RETURN name,type,text;SHOW FUNCTIONS YIELD nameWHERE name IN ['apoc.map.merge','apoc.text.clean','apoc.coll.toSet']RETURN name ORDER BY name;SHOW PROCEDURES YIELD nameWHERE name='apoc.path.expandConfig'RETURN name;
Verification checklist: record whether each helper exists in your exact Core build; run the pure Cypher and APOC variants on the same fixture; compare rows and values; then inspect plan/cardinality separately rather than inferring performance from shorter query text.
7. Production judgment
Every utility moved into the server JVM inherits the server upgrade, memory and security lifecycle. A two-line APOC helper is justified when it makes a stable, well-tested requirement clearer or avoids expensive client round trips. It is not justified merely because it is shorter than native Cypher.
Check your understanding
- What cardinality difference separates an APOC function from a path procedure?
- Why prefer native temporal functions for new Cypher 25 work?
-
Does
limitalone make a path expansion cheap? - Why test dense graph fixtures?
- What evidence should justify an APOC utility?
Review the answers
1. A function returns a value within the current row; a procedure can emit zero or many rows through CALL/YIELD.
2. They are part of the language contract and reduce plugin/deprecation coupling.
3. No. Work can still expand before limiting; selective anchors, bounds and filters remain important.
4. Low-degree examples can hide combinatorial row growth and memory/latency problems.
5. Equivalent-result tests plus a clear maintainability, expressiveness, round-trip or measured performance benefit.
Summary and next step
APOC Core is most useful when it fills a specific language/tooling gap. Lesson 3 raises the risk level by crossing file and network boundaries for JSON integration and export.
Authoritative references
- Current Neo4j versions — Release/LTS snapshot used for this chapter.
- APOC Core 2026.07 documentation — Current officially supported APOC Core manual.
- APOC installation and compatibility — Matching Neo4j/APOC year-month lines, deployment and restart requirements.
- APOC introduction: Core vs Extended — Support boundary between officially supported Core and community-maintained Extended.
- APOC security guidelines — Allowlist/unrestricted execution, file/network and SSRF guidance.
- APOC configuration — apoc.conf location, file/HTTP/trigger-related settings and defaults.
- APOC procedures and functions — Current procedure/function catalog and Cypher 25 status.
- Neo4j Java Reference: extending Neo4j — Custom procedures, functions and server extension boundaries.
- APOC path expander — Path-expansion procedures and control dimensions.
- APOC expandConfig — Detailed bounded path expansion configuration.