Neo4j 4.4 to 5.x upgrades are a significant jump, and the biggest surprise is how much the underlying query language engine has changed, making some of your old Cypher queries silently return different results or error out entirely.

Let’s see what a typical migration looks like. Imagine we have a simple graph with Person nodes and LIKES relationships.

// In Neo4j 4.4
MATCH (p:Person)
RETURN p.name, p.age;

MATCH (p1:Person)-[:LIKES]->(p2:Person)
RETURN p1.name AS giver, p2.name AS receiver;

Now, let’s simulate a migration. We’ll set up a small graph and then attempt to run the same queries in a Neo4j 5 environment after a hypothetical migration.

First, the setup in Neo4j 4.4:

CREATE (alice:Person {name: 'Alice', age: 30})
CREATE (bob:Person {name: 'Bob', age: 25})
CREATE (charlie:Person {name: 'Charlie', age: 35})
CREATE (alice)-[:LIKES]->(bob)
CREATE (bob)-[:LIKES]->(charlie)
CREATE (charlie)-[:LIKES]->(alice);

If you were to run the p.age query in Neo4j 5 without any migration steps, you might get an error like: Invalid input 'a': expected ')' or whitespace if you tried to access p.age directly as a property. This is because Neo4j 5 has moved towards more explicit property access.

The core problem Neo4j 5 addresses is the ambiguity and potential for performance issues in how graph data is accessed and updated. It introduces a more robust and predictable query execution plan by making property access explicit. This means you can’t just assume a property exists and is of a certain type; you need to declare it.

Here’s how you’d typically migrate your data and queries:

  1. Backup Your Database: Always start with a full backup. Use neo4j-admin dump or your cloud provider’s backup mechanism.

  2. Update Neo4j Version: Stop your Neo4j 4.4 instance and install Neo4j 5.x.

  3. Run the neo4j-migrate Tool: This is the primary tool for handling breaking changes. You’ll typically run this from your Neo4j installation’s bin directory.

    # Navigate to your Neo4j 5.x bin directory
    cd /path/to/neo4j-5.x/bin
    
    # Run the migration tool
    ./neo4j-migrate upgrade --from-path /path/to/neo4j-4.4.x/data/databases/graph.db --to-path /path/to/neo4j-5.x/data/databases/graph.db
    

    This tool analyzes your database and attempts to automatically convert incompatible parts of your data and schema to the Neo4j 5 format. It’s not magic; it handles common cases, but complex queries or custom procedures might still need manual intervention.

  4. Address Cypher Breaking Changes: This is where most of the work is.

    • Property Access: The most common breaking change is how properties are accessed. In Neo4j 4.4, p.age was often sufficient. In Neo4j 5, you’ll often need to be more explicit, especially if you’re dealing with potential null values or different data types. For example, if age was a property, you might need to ensure it’s treated as a number.

      • Diagnosis: Run your queries against the migrated database. Look for errors like Invalid input 'a': expected ')' or whitespace or Type mismatch: expected Integer but was String.
      • Fix: Use explicit casting or property checking. For age, you’d likely change p.age to toInteger(p.age) or p.age IS NOT NULL if you want to avoid errors with missing properties.
        // In Neo4j 5
        MATCH (p:Person)
        WHERE p.age IS NOT NULL
        RETURN p.name, toInteger(p.age); // Explicitly cast to Integer
        
      • Why it works: Neo4j 5’s query engine is stricter about types and property existence to prevent runtime errors and ensure predictable query plans. Explicit casting or checking ensures that you’re always working with the expected data type and that missing properties don’t cause unexpected behavior.
    • Implicit RETURN: In Neo4j 4.4, MATCH (n) RETURN n would return the entire node, including all its properties. In Neo4j 5, this is no longer the default for performance and clarity.

      • Diagnosis: Queries that previously returned entire nodes might now return nothing or throw errors related to missing return items.
      • Fix: Explicitly return the properties you need.
        // In Neo4j 5
        MATCH (p:Person)
        RETURN p.name, p.age; // Explicitly return name and age
        
      • Why it works: By forcing explicit returns, Neo4j 5 ensures that queries only fetch the data they absolutely require, leading to better performance and reduced network traffic.
    • Procedure Signatures: If you use any APOC or custom procedures, their signatures might have changed.

      • Diagnosis: Errors like Unknown procedure ... or Procedure ... requires N arguments but received M.
      • Fix: Consult the documentation for the specific procedures you’re using and update your calls to match the new signatures. For example, apoc.create.node might have changed its argument order or required explicit types.
      • Why it works: Procedure APIs are updated to align with Neo4j 5’s core changes, often requiring more specific input parameters for robustness.
    • Index and Constraint Syntax: While largely backward compatible, some very old syntax might be deprecated.

      • Diagnosis: Deprecation warnings or errors related to index/constraint creation.
      • Fix: Use the modern syntax. For example, CREATE INDEX ON :Person(name) is preferred over older forms.
      • Why it works: Neo4j standardizes its DDL (Data Definition Language) for better consistency and future extensibility.
    • Security and Authentication: Neo4j 5 has enhanced security features, which might require updates to authentication configurations or user management.

      • Diagnosis: Connection refused errors, authentication failures, or messages about unsupported authentication methods.
      • Fix: Update your neo4j.conf file with the new security settings, especially dbms.security.auth_enabled and related parameters. Consider migrating to RBAC (Role-Based Access Control) if you haven’t already.
      • Why it works: Neo4j 5 prioritizes a more secure default configuration and offers more granular access control, requiring explicit configuration for authentication and authorization.
  5. Test Thoroughly: Run your application’s test suite against the migrated database. Pay close attention to queries that involve property access, aggregations, and any custom procedures.

After a successful migration, you’ll find that your queries are more predictable and that Neo4j 5’s performance optimizations can be fully leveraged. The next hurdle you’ll likely encounter is understanding the new Fabric and multi-database capabilities.

Want structured learning?

Take the full Neo4j course →