User-defined functions
User-defined functions are simpler forms of procedures that return a single value and are read-only. Although they are less powerful in capability, they are often easier to use and more efficient than procedures for many common tasks. For a comparison between user-defined procedures, functions, and aggregation functions see Neo4j customized code.
Call a user-defined function
User-defined functions are called in the same way as any other Cypher function.
The function name must be fully qualified, so a function named join defined in the package org.neo4j.examples could be called using:
MATCH (p: Person) WHERE p.age = 36
RETURN org.neo4j.examples.join(collect(p.names))
Create a function
User-defined functions are created similarly to how procedures are created.
But unlike procedures, they are annotated with @UserFunction and return a single value instead of a stream of values.
Particular things to note:
-
All functions are annotated with
@UserFunction. -
The function name must be namespaced and is not allowed in reserved namespaces.
-
If a 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.UserFunction.
|
The correct way to signal an error from within a function is to throw |
package example;
import java.util.List;
import org.neo4j.procedure.Description;
import org.neo4j.procedure.Name;
import org.neo4j.procedure.UserFunction;
public class Join
{
@UserFunction
@Description("example.join(['s1','s2',...], delimiter) - join the given strings with the given delimiter.")
public String join(
@Name("strings") List<String> strings,
@Name(value = "delimiter", defaultValue = ",") String delimiter) {
if (strings == null || delimiter == null) {
return null;
}
return String.join(delimiter, strings);
}
}
Integration tests
Tests for user-defined functions are created in the same way as those for procedures.
package example;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.neo4j.driver.Driver;
import org.neo4j.driver.GraphDatabase;
import org.neo4j.driver.Session;
import org.neo4j.harness.Neo4j;
import org.neo4j.harness.Neo4jBuilders;
import static org.assertj.core.api.Assertions.assertThat;
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class JoinTest {
private Neo4j embeddedDatabaseServer;
@BeforeAll
void initializeNeo4j() {
this.embeddedDatabaseServer = Neo4jBuilders.newInProcessBuilder()
.withDisabledServer()
.withFunction(Join.class)
.build();
}
@AfterAll
void closeNeo4j() {
this.embeddedDatabaseServer.close();
}
@Test
void joinsStrings() {
// This is in a try-block, to make sure we close the driver after the test
try(Driver driver = GraphDatabase.driver(embeddedDatabaseServer.boltURI());
Session session = driver.session()) {
// When
String result = session.run( "RETURN example.join(['Hello', 'World']) AS result").single().get("result").asString();
// Then
assertThat( result).isEqualTo(( "Hello,World" ));
}
}
}
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.
| Reserved | Deprecated in Cypher 25 since Neo4j 2025.11 |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|