User-defined aggregation functions

User-defined aggregation functions are functions that aggregate data and return a single result. For a comparison between user-defined procedures, functions, and aggregation functions, see Neo4j customized code.

Call an aggregation function

User-defined aggregation functions are called in the same way as any other Cypher aggregation function. The function name must be fully qualified, so a function named longestString defined in the package org.neo4j.examples could be called using:

MATCH (p: Person) WHERE p.age = 36
RETURN org.neo4j.examples.longestString(p.name)

Writing a user-defined aggregation function

User-defined aggregation functions are annotated with @UserAggregationFunction. The annotated function must return an instance of an aggregator class. An aggregator class contains one method annotated with @UserAggregationUpdate and one method annotated with @UserAggregationResult. The method annotated with @UserAggregationUpdate will be called multiple times and enables the class to aggregate data. When the aggregation is done, the method annotated with @UserAggregationResult will be called once and the result of the aggregation will be returned.

Particular things to note:

  • All functions are annotated with @UserAggregationFunction.

  • The aggregation function name must be namespaced and is not allowed in reserved namespaces.

  • If a user-defined aggregation function is registered with the same name as a built-in function in a deprecated namespace, the built-in function is shadowed.

See Values and types for details on values and types.

For more details, see the Neo4j Javadocs for org.neo4j.procedure.UserAggregationFunction.

The correct way to signal an error from within an aggregation function is to throw RuntimeException.

package example;

import org.neo4j.procedure.Description;
import org.neo4j.procedure.Name;
import org.neo4j.procedure.UserAggregationFunction;
import org.neo4j.procedure.UserAggregationResult;
import org.neo4j.procedure.UserAggregationUpdate;

public class LongestString
{
    @UserAggregationFunction
    @Description( "org.neo4j.function.example.longestString(string) - aggregates the longest string found" )
    public LongStringAggregator longestString()
    {
        return new LongStringAggregator();
    }

    public static class LongStringAggregator
    {
        private int longest;
        private String longestString;

        @UserAggregationUpdate
        public void findLongest(
                @Name( "string" ) String string )
        {
            if ( string != null && string.length() > longest)
            {
                longest = string.length();
                longestString = string;
            }
        }

        @UserAggregationResult
        public String result()
        {
            return longestString;
        }
    }
}

Integration tests

Tests for user-defined aggregation functions are created in the same way as those for normal user-defined functions.

A template for testing a user-defined aggregation function that finds the longest string.
package example;

import org.junit.Rule;
import org.junit.Test;
import org.neo4j.driver.v1.*;
import org.neo4j.harness.junit.Neo4jRule;

import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assert.assertThat;

public class LongestStringTest
{
    // This rule starts a Neo4j instance
    @Rule
    public Neo4jRule neo4j = new Neo4jRule()

            // This is the function to test
            .withAggregationFunction( LongestString.class );

    @Test
    public void shouldAllowIndexingAndFindingANode() throws Throwable
    {
        // This is in a try-block, to make sure you close the driver after the test
        try( Driver driver = GraphDatabase.driver( neo4j.boltURI() , Config.build().withEncryptionLevel( Config.EncryptionLevel.NONE ).toConfig() ) )
        {
            // Given
            Session session = driver.session();

            // When
            String result = session.run( "UNWIND ["abc", "abcd", "ab"] AS string RETURN example.longestString(string) AS result").single().get("result").asString();

            // Then
            assertThat( result, equalTo( "abcd" ) );
        }
    }
}

Reserved and deprecated function namespaces

Note that deprecated function namespaces will be moved to reserved in the next major Cypher version. For more information about Neo4j and Cypher versioning, see Operations manual → Introduction.

Table 1. Overview of reserved and deprecated function namespaces and names
Reserved Deprecated in Cypher 25 since Neo4j 2025.11

*

abac.*

date

builtin.*

date.realtime

cdc.*

date.statement

coll.*

date.transaction

date.*

date.truncate

datetime.*

datetime

db.*

datetime.fromepoch

dbms.*

datetime.fromepochmillis

duration.*

datetime.realtime

graph.*

datetime.statement

internal.*

datetime.transaction

localdatetime.*

datetime.truncate

localtime.*

db.nameFromElementId

math.*

duration

plugin.*

duration.between

point.*

duration.inDays

stored.*

duration.inMonths

string.*

duration.inSeconds

time.*

graph.byElementId

tx.*

graph.byName

unsupported.*

graph.names

vector.*

graph.propertiesByName

localdatetime

localdatetime.realtime

localdatetime.statement

localdatetime.transaction

localdatetime.truncate

localtime

localtime.realtime

localtime.statement

localtime.transaction

localtime.truncate

point.distance

point.withinBBox

time

time.realtime

time.statement

time.transaction

time.truncate

vector.similarity.cosine

vector.similarity.euclidean