From a25bd65b5cc9057382bf2d1393eb1c1bc5602842 Mon Sep 17 00:00:00 2001 From: Louise Berglund Date: Tue, 1 Jul 2025 15:01:00 +0200 Subject: [PATCH 01/14] Draft of GQLSTATUS error handling for Java --- .../modules/ROOT/pages/query-simple.adoc | 87 ++++++++++++++++++- 1 file changed, 83 insertions(+), 4 deletions(-) diff --git a/java-manual/modules/ROOT/pages/query-simple.adoc b/java-manual/modules/ROOT/pages/query-simple.adoc index 0ca64dab..64c7df95 100644 --- a/java-manual/modules/ROOT/pages/query-simple.adoc +++ b/java-manual/modules/ROOT/pages/query-simple.adoc @@ -183,11 +183,86 @@ A query run may fail for a number of reasons, with different link:https://neo4j. Some of them are due to Cypher syntax errors, permission issues, or other forms of misconfiguration/misusage. The choice of how to handle those exceptions is up to your application: whether you want to be defensive (for example check if there are records to process to avoid link:https://neo4j.com/docs/api/java-driver/{java-driver-version}/org.neo4j.driver/org/neo4j/driver/exceptions/NoSuchRecordException.html[NoSuchRecordException]), or whether you want to catch and handle the exceptions as they arise. +All exceptions thrown by the Java driver are subclasses of link:https://neo4j.com/docs/api/java-driver/{java-driver-version}/org.neo4j.driver/org/neo4j/driver/exceptions/Neo4jException.html[Neo4jException]. +When catching and handling exceptions, you can choose to catch all exceptions of class Neo4jException, or a more specific exception class. +As of Java driver 5.26, each Neo4jException has a GQLSTATUS error code, which is stable and can thus be relied on in try-catch patterns. + +[source, java, test-skip] +---- +// import org.neo4j.driver.QueryConfig; + +driver.executableQuery("CREATE CONSTRAINT userConstraint FOR (user:User) REQUIRE (user.name) IS UNIQUE") + .withConfig(QueryConfig.builder().withDatabase("neo4j").build()) + .execute(); + +try { + for(String user: userInputList) { + driver.executableQuery("CREATE (:User {name:$username})") + .withConfig(QueryConfig.builder().withDatabase("neo4j").build()) + .withParameters(Map.of("username", user)) + .execute(); + } +} catch (ClientException e) { + if (e.gqStatus.equals("22N79")) { + System.out.println("Username is already taken"); + } +} +---- + +[NOTE] +==== +The default GQLSTATUS code 50N42 is returned when an exception does not have a GQL-status object. +This can happen if the driver is connected to an older Neo4j server. +It is important not to rely on this default code, as future Neo4j server versions might change it by adding an appropriate GQL-status object to the exception. +==== + +A Neo4jException also has an optional GQL-status object cause, which in turn is a Neo4jException. +Sometimes, the relevant GQLSTATUS code is situated in a GQLSTATUS-object cause, and the whole cause chain needs to be inspected: + +[source, java, test-skip] +---- +// import java.util.Map; +// import org.neo4j.driver.QueryConfig; +var result; + +try { + result = driver.executableQuery("GRANT ROLE $role TO $user") + .withConfig(QueryConfig.builder().withDatabase("system").build()) + .withParameters(Map.of("user", username, "role", roleName)) + .execute(); +} catch (Neo4jException e) { + if (hasGqlStatus(e, "42N10")) { + // special handling of the role not existing + ... + } else if (hasGqlStatus(e, "42N09")) { + // special handling of the user not existing + ... + } else { + // handling of all other errors + ... + } +} + +public boolean hasGqlStatus(Neo4jException exception, String... codes) { + var c = Set.of(codes); + + Optional e = Optional.ofNullable(exception); + while (e.isPresent()) { + if (codes.contains(e.gqlStatus())) { + return true; + } + e = e.gqlCause(); + } + return false; +} +---- + +For more information on Neo4j error handling and GQLSTATUS error codes, see link:https://neo4j.com/docs/status-codes/current/errors[the status codes manual]. + [TIP] The driver automatically retries to run a failed query, if the failure is deemed to be transient (for example due to temporary server unavailability). An exception will be raised if the operation keeps failing after a number of attempts. - == Query configuration You can supply further configuration parameters to alter the default behavior of `.executableQuery()`. @@ -359,9 +434,13 @@ public class App { result.summary().query(), result.records().size(), result.summary().resultAvailableAfter(TimeUnit.MILLISECONDS)); - } catch (Exception e) { - System.out.println(e.getMessage()); - System.exit(1); + } catch (Neo4jException e) { + if (e.gqlSatus().equals("42NFF")) { + System.out.println("There was a permission issue. Make sure you have correct permissions and try again."); + } else { + System.out.println(e.getMessage()); + System.exit(1); + } } } } From 67f32e9b23552c04905efe7023054469475c4f26 Mon Sep 17 00:00:00 2001 From: Stefano Ottolenghi Date: Mon, 7 Jul 2025 16:08:35 +0200 Subject: [PATCH 02/14] expand --- .../modules/ROOT/pages/query-simple.adoc | 154 +++++++++++------- 1 file changed, 95 insertions(+), 59 deletions(-) diff --git a/java-manual/modules/ROOT/pages/query-simple.adoc b/java-manual/modules/ROOT/pages/query-simple.adoc index 64c7df95..e73a5d2e 100644 --- a/java-manual/modules/ROOT/pages/query-simple.adoc +++ b/java-manual/modules/ROOT/pages/query-simple.adoc @@ -177,91 +177,127 @@ There can be circumstances where your query structure prevents the usage of para For those rare use cases, see xref:query-advanced#_dynamic_values_in_property_keys_relationship_types_and_labels[Dynamic values in property keys, relationship types, and labels]. +[#error-handling] == Error handling A query run may fail for a number of reasons, with different link:https://neo4j.com/docs/api/java-driver/{java-driver-version}/org.neo4j.driver/org/neo4j/driver/exceptions/package-summary.html[exceptions] being raised. -Some of them are due to Cypher syntax errors, permission issues, or other forms of misconfiguration/misusage. -The choice of how to handle those exceptions is up to your application: whether you want to be defensive (for example check if there are records to process to avoid link:https://neo4j.com/docs/api/java-driver/{java-driver-version}/org.neo4j.driver/org/neo4j/driver/exceptions/NoSuchRecordException.html[NoSuchRecordException]), or whether you want to catch and handle the exceptions as they arise. +All exceptions are subclasses of link:https://neo4j.com/docs/api/java-driver/{java-driver-version}/org.neo4j.driver/org/neo4j/driver/exceptions/Neo4jException.html[`Neo4jException`]. +You can use an exception's code (retrievable with `.code()`) or GQL status (retrievable with `.gqlStatus()`) to stably identify a specific error. -All exceptions thrown by the Java driver are subclasses of link:https://neo4j.com/docs/api/java-driver/{java-driver-version}/org.neo4j.driver/org/neo4j/driver/exceptions/Neo4jException.html[Neo4jException]. -When catching and handling exceptions, you can choose to catch all exceptions of class Neo4jException, or a more specific exception class. -As of Java driver 5.26, each Neo4jException has a GQLSTATUS error code, which is stable and can thus be relied on in try-catch patterns. +Exception objects also expose methods that make them GQL-compliant. +The actual _cause_ that triggered the exception is sometimes found in the optional GQL-status object retrievable with `.gqlCause()`, which is itself a `Neo4jException`. -[source, java, test-skip] +.Usage of `Neo4jException` methods +[source, java] ---- -// import org.neo4j.driver.QueryConfig; +// import import org.neo4j.driver.exceptions.Neo4jException; -driver.executableQuery("CREATE CONSTRAINT userConstraint FOR (user:User) REQUIRE (user.name) IS UNIQUE") +try { + var result = driver.executableQuery("MATCH (p:Person) RETURN ") .withConfig(QueryConfig.builder().withDatabase("neo4j").build()) .execute(); - -try { - for(String user: userInputList) { - driver.executableQuery("CREATE (:User {name:$username})") - .withConfig(QueryConfig.builder().withDatabase("neo4j").build()) - .withParameters(Map.of("username", user)) - .execute(); - } -} catch (ClientException e) { - if (e.gqStatus.equals("22N79")) { - System.out.println("Username is already taken"); - } +} catch (Neo4jException e) { + System.out.printf("Exception code: %s\n", e.code()); + System.out.printf("Exception classification: %s\n", e.rawClassification()); + System.out.printf("Exception message: %s\n", e); + + // GQL-compliant methods + System.out.printf("Exception GQL status: %s\n", e.gqlStatus()); + System.out.printf("Exception status description: %s\n", e.statusDescription()); + System.out.printf("Exception GQL cause: %s\n", e.gqlCause()); + System.out.printf("Exception GQL diagnostic record: %s\n", e.diagnosticRecord()); } ----- -[NOTE] -==== -The default GQLSTATUS code 50N42 is returned when an exception does not have a GQL-status object. -This can happen if the driver is connected to an older Neo4j server. -It is important not to rely on this default code, as future Neo4j server versions might change it by adding an appropriate GQL-status object to the exception. -==== +/* +Exception code: Neo.ClientError.Statement.SyntaxError +Exception classification: Optional[CLIENT_ERROR] +Exception message: org.neo4j.driver.exceptions.ClientException: Invalid input '': expected an expression, '*', 'ALL' or 'DISTINCT' (line 1, column 24 (offset: 23)) +"MATCH (p:Person) RETURN" + ^ +Exception GQL status: 42001 +Exception status description: error: syntax error or access rule violation - invalid syntax +Exception GQL cause: Optional[org.neo4j.driver.exceptions.Neo4jException: 42I06: Invalid input '', expected: an expression, '*', 'ALL' or 'DISTINCT'.] +Exception GQL diagnostic record: {_classification="CLIENT_ERROR", OPERATION_CODE="0", OPERATION="", CURRENT_SCHEMA="/", _position={column: 24, offset: 23, line: 1}} +*/ +---- -A Neo4jException also has an optional GQL-status object cause, which in turn is a Neo4jException. -Sometimes, the relevant GQLSTATUS code is situated in a GQLSTATUS-object cause, and the whole cause chain needs to be inspected: +GQL status codes are particularly helpful when you want your application to behave differently depending on the exact error that was raised by the server. -[source, java, test-skip] +.Distinguishing between different exception codes +[source, java] ---- -// import java.util.Map; -// import org.neo4j.driver.QueryConfig; -var result; +package demo; -try { - result = driver.executableQuery("GRANT ROLE $role TO $user") - .withConfig(QueryConfig.builder().withDatabase("system").build()) - .withParameters(Map.of("user", username, "role", roleName)) - .execute(); -} catch (Neo4jException e) { - if (hasGqlStatus(e, "42N10")) { - // special handling of the role not existing - ... - } else if (hasGqlStatus(e, "42N09")) { - // special handling of the user not existing - ... - } else { - // handling of all other errors - ... +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import org.neo4j.driver.AuthTokens; +import org.neo4j.driver.Driver; +import org.neo4j.driver.GraphDatabase; +import org.neo4j.driver.QueryConfig; +import org.neo4j.driver.exceptions.Neo4jException; + +public class App { + + final String dbUri = "{neo4j-database-uri}"; + final String dbUser = "{neo4j-username}"; + final String dbPassword = "{neo4j-password}"; + + public static void main(String... args) { + try (var driver = GraphDatabase.driver(dbUri, AuthTokens.basic(dbUser, dbPassword))) { + try { + var result = driver.executableQuery("CREATE (p:Person {name: $name}) TURN p") + .withParameters(Map.of("name", "Frida")) + .withConfig(QueryConfig.builder().withDatabase("neo4j").build()) + .execute(); + } catch (Neo4jException e) { + if (hasGqlStatus(e, "42001")) { + // Neo.ClientError.Statement.SyntaxError + // special handling of syntax error in query + System.out.println(e); + } else if (hasGqlStatus(e, "42NFF")) { + // Neo.ClientError.Security.Forbidden + // special handling of user not having CREATE permissions + System.out.println(e); + } else { + // handling of all other exceptions + System.out.println(e); + } + } + } } -} -public boolean hasGqlStatus(Neo4jException exception, String... codes) { - var c = Set.of(codes); + public static boolean hasGqlStatus(Neo4jException exception, String... codes) { + var c = Set.of(codes); - Optional e = Optional.ofNullable(exception); - while (e.isPresent()) { - if (codes.contains(e.gqlStatus())) { - return true; + Optional e = Optional.ofNullable(exception); + while (e.isPresent()) { + if (c.contains(e.get().gqlStatus())) { + return true; + } + e = e.get().gqlCause(); + } + return false; } - e = e.gqlCause(); - } - return false; } ---- -For more information on Neo4j error handling and GQLSTATUS error codes, see link:https://neo4j.com/docs/status-codes/current/errors[the status codes manual]. +For a full list of GQL-status error codes, see link:https://neo4j.com/docs/status-codes/current/errors[the status codes manual]. + +[NOTE] +==== +The GQL status code `50N42` is returned when an exception does not have a GQL-status object. +This can happen if the driver is connected to an older Neo4j server. +Don't rely on this status code, as future Neo4j server versions might change it with a more appropriate one. +==== [TIP] +==== The driver automatically retries to run a failed query, if the failure is deemed to be transient (for example due to temporary server unavailability). An exception will be raised if the operation keeps failing after a number of attempts. +==== + == Query configuration From e87bddcf77c66cb3cea5df68a1dff2228a6a4451 Mon Sep 17 00:00:00 2001 From: Stefano Ottolenghi Date: Mon, 7 Jul 2025 18:28:01 +0200 Subject: [PATCH 03/14] tweaks --- java-manual/modules/ROOT/pages/query-simple.adoc | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/java-manual/modules/ROOT/pages/query-simple.adoc b/java-manual/modules/ROOT/pages/query-simple.adoc index e73a5d2e..207370ab 100644 --- a/java-manual/modules/ROOT/pages/query-simple.adoc +++ b/java-manual/modules/ROOT/pages/query-simple.adoc @@ -197,9 +197,9 @@ try { .withConfig(QueryConfig.builder().withDatabase("neo4j").build()) .execute(); } catch (Neo4jException e) { - System.out.printf("Exception code: %s\n", e.code()); + System.out.printf("Neo4j error code: %s\n", e.code()); System.out.printf("Exception classification: %s\n", e.rawClassification()); - System.out.printf("Exception message: %s\n", e); + System.out.printf("Exception message: %s\n", e.getMessage()); // GQL-compliant methods System.out.printf("Exception GQL status: %s\n", e.gqlStatus()); @@ -209,9 +209,9 @@ try { } /* -Exception code: Neo.ClientError.Statement.SyntaxError +Neo4j error code: Neo.ClientError.Statement.SyntaxError Exception classification: Optional[CLIENT_ERROR] -Exception message: org.neo4j.driver.exceptions.ClientException: Invalid input '': expected an expression, '*', 'ALL' or 'DISTINCT' (line 1, column 24 (offset: 23)) +Exception message: Invalid input '': expected an expression, '*', 'ALL' or 'DISTINCT' (line 1, column 24 (offset: 23)) "MATCH (p:Person) RETURN" ^ Exception GQL status: 42001 @@ -255,14 +255,14 @@ public class App { if (hasGqlStatus(e, "42001")) { // Neo.ClientError.Statement.SyntaxError // special handling of syntax error in query - System.out.println(e); + System.out.println(e.getMessage()); } else if (hasGqlStatus(e, "42NFF")) { // Neo.ClientError.Security.Forbidden // special handling of user not having CREATE permissions - System.out.println(e); + System.out.println(e.getMessage()); } else { // handling of all other exceptions - System.out.println(e); + System.out.println(e.getMessage()); } } } From d4a63395a0716ba0f194d91c8831e7686dec7d65 Mon Sep 17 00:00:00 2001 From: Stefano Ottolenghi Date: Mon, 7 Jul 2025 18:32:58 +0200 Subject: [PATCH 04/14] fix tests --- java-manual/modules/ROOT/pages/query-simple.adoc | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/java-manual/modules/ROOT/pages/query-simple.adoc b/java-manual/modules/ROOT/pages/query-simple.adoc index 207370ab..6941941d 100644 --- a/java-manual/modules/ROOT/pages/query-simple.adoc +++ b/java-manual/modules/ROOT/pages/query-simple.adoc @@ -240,9 +240,9 @@ import org.neo4j.driver.exceptions.Neo4jException; public class App { - final String dbUri = "{neo4j-database-uri}"; - final String dbUser = "{neo4j-username}"; - final String dbPassword = "{neo4j-password}"; + private static final String dbUri = "{neo4j-database-uri}"; + private static final String dbUser = "{neo4j-username}"; + private static final String dbPassword = "{neo4j-password}"; public static void main(String... args) { try (var driver = GraphDatabase.driver(dbUri, AuthTokens.basic(dbUser, dbPassword))) { @@ -405,13 +405,15 @@ import org.neo4j.driver.GraphDatabase; import org.neo4j.driver.Record; import org.neo4j.driver.QueryConfig; import org.neo4j.driver.RoutingControl; +import org.neo4j.driver.exceptions.Neo4jException; public class App { + private static final String dbUri = "{neo4j-database-uri}"; + private static final String dbUser = "{neo4j-username}"; + private static final String dbPassword = "{neo4j-password}"; + public static void main(String... args) { - final String dbUri = "{neo4j-database-uri}"; - final String dbUser = "{neo4j-username}"; - final String dbPassword = "{neo4j-password}"; try (var driver = GraphDatabase.driver(dbUri, AuthTokens.basic(dbUser, dbPassword))) { From a772c35dd7c374b8a363afd584dd961253dc6c4c Mon Sep 17 00:00:00 2001 From: Stefano Ottolenghi Date: Mon, 7 Jul 2025 19:27:21 +0200 Subject: [PATCH 05/14] another test --- java-manual/modules/ROOT/pages/query-simple.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java-manual/modules/ROOT/pages/query-simple.adoc b/java-manual/modules/ROOT/pages/query-simple.adoc index 6941941d..3828de30 100644 --- a/java-manual/modules/ROOT/pages/query-simple.adoc +++ b/java-manual/modules/ROOT/pages/query-simple.adoc @@ -473,7 +473,7 @@ public class App { result.summary().resultAvailableAfter(TimeUnit.MILLISECONDS)); } catch (Neo4jException e) { - if (e.gqlSatus().equals("42NFF")) { + if (e.gqlStatus().equals("42NFF")) { System.out.println("There was a permission issue. Make sure you have correct permissions and try again."); } else { System.out.println(e.getMessage()); From 83e674cb50f0e4316739b696f03f54f8696cfe4c Mon Sep 17 00:00:00 2001 From: Stefano Ottolenghi Date: Tue, 8 Jul 2025 09:14:26 +0200 Subject: [PATCH 06/14] more clarification --- java-manual/modules/ROOT/pages/query-simple.adoc | 1 + 1 file changed, 1 insertion(+) diff --git a/java-manual/modules/ROOT/pages/query-simple.adoc b/java-manual/modules/ROOT/pages/query-simple.adoc index 3828de30..771ac25d 100644 --- a/java-manual/modules/ROOT/pages/query-simple.adoc +++ b/java-manual/modules/ROOT/pages/query-simple.adoc @@ -186,6 +186,7 @@ You can use an exception's code (retrievable with `.code()`) or GQL status (retr Exception objects also expose methods that make them GQL-compliant. The actual _cause_ that triggered the exception is sometimes found in the optional GQL-status object retrievable with `.gqlCause()`, which is itself a `Neo4jException`. +You might need to recursively traverse the cause chain before reaching the root cause of the exception you caught. .Usage of `Neo4jException` methods [source, java] From 6db93087ffbe180cd453f05e6d5ca00469108903 Mon Sep 17 00:00:00 2001 From: Stefano Ottolenghi Date: Wed, 9 Jul 2025 09:23:16 +0200 Subject: [PATCH 07/14] final --- .../modules/ROOT/pages/query-simple.adoc | 61 +++++++++++-------- 1 file changed, 36 insertions(+), 25 deletions(-) diff --git a/java-manual/modules/ROOT/pages/query-simple.adoc b/java-manual/modules/ROOT/pages/query-simple.adoc index 771ac25d..14fb32b8 100644 --- a/java-manual/modules/ROOT/pages/query-simple.adoc +++ b/java-manual/modules/ROOT/pages/query-simple.adoc @@ -181,15 +181,14 @@ For those rare use cases, see xref:query-advanced#_dynamic_values_in_property_ke == Error handling A query run may fail for a number of reasons, with different link:https://neo4j.com/docs/api/java-driver/{java-driver-version}/org.neo4j.driver/org/neo4j/driver/exceptions/package-summary.html[exceptions] being raised. -All exceptions are subclasses of link:https://neo4j.com/docs/api/java-driver/{java-driver-version}/org.neo4j.driver/org/neo4j/driver/exceptions/Neo4jException.html[`Neo4jException`]. -You can use an exception's code (retrievable with `.code()`) or GQL status (retrievable with `.gqlStatus()`) to stably identify a specific error. +The driver automatically retries to run a failed query, if the failure is deemed to be transient (for example due to temporary server unavailability). +An exception will be raised if the operation keeps failing after a number of attempts. -Exception objects also expose methods that make them GQL-compliant. -The actual _cause_ that triggered the exception is sometimes found in the optional GQL-status object retrievable with `.gqlCause()`, which is itself a `Neo4jException`. -You might need to recursively traverse the cause chain before reaching the root cause of the exception you caught. +All exceptions are subclasses of link:https://neo4j.com/docs/api/java-driver/{java-driver-version}/org.neo4j.driver/org/neo4j/driver/exceptions/Neo4jException.html[`Neo4jException`]. +You can use an exception's code (retrievable with `.code()`) to stably identify a specific error; error messages are instead not stable markers, and should not be relied upon. -.Usage of `Neo4jException` methods -[source, java] +.Basic error handling +[source, java, role=nocollapse] ---- // import import org.neo4j.driver.exceptions.Neo4jException; @@ -201,22 +200,42 @@ try { System.out.printf("Neo4j error code: %s\n", e.code()); System.out.printf("Exception classification: %s\n", e.rawClassification()); System.out.printf("Exception message: %s\n", e.getMessage()); - - // GQL-compliant methods - System.out.printf("Exception GQL status: %s\n", e.gqlStatus()); - System.out.printf("Exception status description: %s\n", e.statusDescription()); - System.out.printf("Exception GQL cause: %s\n", e.gqlCause()); - System.out.printf("Exception GQL diagnostic record: %s\n", e.diagnosticRecord()); } - /* Neo4j error code: Neo.ClientError.Statement.SyntaxError Exception classification: Optional[CLIENT_ERROR] Exception message: Invalid input '': expected an expression, '*', 'ALL' or 'DISTINCT' (line 1, column 24 (offset: 23)) "MATCH (p:Person) RETURN" ^ -Exception GQL status: 42001 -Exception status description: error: syntax error or access rule violation - invalid syntax +*/ +---- + +Exception objects also expose errors as GQL-status objects. +The main difference between link:https://neo4j.com/docs/status-codes/current/errors/all-errors/[Neo4j error codes] and link:https://neo4j.com/docs/status-codes/current/errors/gql-errors/[GQL error codes] is that the GQL ones are more granular: a single Neo4j error code might be broken in several, more specific GQL error codes. + +The actual _cause_ that triggered an exception is sometimes found in the optional GQL-status object retrievable with `.gqlCause()`, which is itself a `Neo4jException`. +You might need to recursively traverse the cause chain before reaching the root cause of the exception you caught. +In the example below, the exception's GQL status code is `42001`, but the actual source of the error has status code `42I06`. + +.Usage of `Neo4jException` with GQL-related methods +[source, java] +---- +// import import org.neo4j.driver.exceptions.Neo4jException; + +try { + var result = driver.executableQuery("MATCH (p:Person) RETURN ") + .withConfig(QueryConfig.builder().withDatabase("neo4j").build()) + .execute(); +} catch (Neo4jException e) { + System.out.printf("Exception GQL status code: %s\n", e.gqlStatus()); + System.out.printf("Exception GQL status description: %s\n", e.statusDescription()); + System.out.printf("Exception GQL cause: %s\n", e.gqlCause()); + System.out.printf("Exception GQL diagnostic record: %s\n", e.diagnosticRecord()); +} + +/* +Exception GQL status code: 42001 +Exception GQL status description: error: syntax error or access rule violation - invalid syntax Exception GQL cause: Optional[org.neo4j.driver.exceptions.Neo4jException: 42I06: Invalid input '', expected: an expression, '*', 'ALL' or 'DISTINCT'.] Exception GQL diagnostic record: {_classification="CLIENT_ERROR", OPERATION_CODE="0", OPERATION="", CURRENT_SCHEMA="/", _position={column: 24, offset: 23, line: 1}} */ @@ -248,7 +267,7 @@ public class App { public static void main(String... args) { try (var driver = GraphDatabase.driver(dbUri, AuthTokens.basic(dbUser, dbPassword))) { try { - var result = driver.executableQuery("CREATE (p:Person {name: $name}) TURN p") + var result = driver.executableQuery("CREATE (p:Person {name: $name}) RETURN ") .withParameters(Map.of("name", "Frida")) .withConfig(QueryConfig.builder().withDatabase("neo4j").build()) .execute(); @@ -284,8 +303,6 @@ public class App { } ---- -For a full list of GQL-status error codes, see link:https://neo4j.com/docs/status-codes/current/errors[the status codes manual]. - [NOTE] ==== The GQL status code `50N42` is returned when an exception does not have a GQL-status object. @@ -293,12 +310,6 @@ This can happen if the driver is connected to an older Neo4j server. Don't rely on this status code, as future Neo4j server versions might change it with a more appropriate one. ==== -[TIP] -==== -The driver automatically retries to run a failed query, if the failure is deemed to be transient (for example due to temporary server unavailability). -An exception will be raised if the operation keeps failing after a number of attempts. -==== - == Query configuration From 912ef784f71bda3d0fd33afc9417ab9a1cd56079 Mon Sep 17 00:00:00 2001 From: Stefano Ottolenghi Date: Wed, 9 Jul 2025 10:06:34 +0200 Subject: [PATCH 08/14] move data types exceptions content into error-handling --- java-manual/modules/ROOT/pages/data-types.adoc | 11 ----------- java-manual/modules/ROOT/pages/query-simple.adoc | 11 +++++++++-- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/java-manual/modules/ROOT/pages/data-types.adoc b/java-manual/modules/ROOT/pages/data-types.adoc index 22fd62e3..68c85e29 100644 --- a/java-manual/modules/ROOT/pages/data-types.adoc +++ b/java-manual/modules/ROOT/pages/data-types.adoc @@ -461,17 +461,6 @@ public class App { For full documentation, see link:https://neo4j.com/docs/api/java-driver/{java-driver-version}/org.neo4j.driver/org/neo4j/driver/internal/value/PathValue.html[API documentation -> PathValue]. -== Exceptions - -The driver can raise a number of different exceptions, see link:https://neo4j.com/docs/api/java-driver/{java-driver-version}/org.neo4j.driver/org/neo4j/driver/exceptions/package-summary.html[API documentation -> Exceptions]. -For a list of errors the server can return, see link:{neo4j-docs-base-uri}/status-codes/{page-version}[Status codes]. - -Some server errors are marked as safe to retry without need to alter the original request. -Examples of such errors are deadlocks, memory issues, or connectivity issues. -Driver's exceptions implementing link:https://neo4j.com/docs/api/java-driver/{java-driver-version}/org.neo4j.driver/org/neo4j/driver/exceptions/RetryableException.html[`RetryableException`] are such that a further attempt at the operation that caused it might be successful. -This is particular useful when running queries in xref:transactions#explicit-transactions[explicit transactions], to know if a failed query is worth re-running. - - ifndef::backend-pdf[] [discrete.glossary] == Glossary diff --git a/java-manual/modules/ROOT/pages/query-simple.adoc b/java-manual/modules/ROOT/pages/query-simple.adoc index 14fb32b8..117f1e17 100644 --- a/java-manual/modules/ROOT/pages/query-simple.adoc +++ b/java-manual/modules/ROOT/pages/query-simple.adoc @@ -181,10 +181,10 @@ For those rare use cases, see xref:query-advanced#_dynamic_values_in_property_ke == Error handling A query run may fail for a number of reasons, with different link:https://neo4j.com/docs/api/java-driver/{java-driver-version}/org.neo4j.driver/org/neo4j/driver/exceptions/package-summary.html[exceptions] being raised. -The driver automatically retries to run a failed query, if the failure is deemed to be transient (for example due to temporary server unavailability). +When using `Driver.executableQuery()`, the driver automatically retries to run a failed query, if the failure is deemed to be transient (for example due to temporary server unavailability). An exception will be raised if the operation keeps failing after a number of attempts. -All exceptions are subclasses of link:https://neo4j.com/docs/api/java-driver/{java-driver-version}/org.neo4j.driver/org/neo4j/driver/exceptions/Neo4jException.html[`Neo4jException`]. +All exceptions coming from the server are subclasses of link:https://neo4j.com/docs/api/java-driver/{java-driver-version}/org.neo4j.driver/org/neo4j/driver/exceptions/Neo4jException.html[`Neo4jException`]. You can use an exception's code (retrievable with `.code()`) to stably identify a specific error; error messages are instead not stable markers, and should not be relied upon. .Basic error handling @@ -310,6 +310,13 @@ This can happen if the driver is connected to an older Neo4j server. Don't rely on this status code, as future Neo4j server versions might change it with a more appropriate one. ==== +[TIP] +==== +Transient server errors can be retried without need to alter the original request. +Exceptions implementing link:https://neo4j.com/docs/api/java-driver/{java-driver-version}/org.neo4j.driver/org/neo4j/driver/exceptions/RetryableException.html[`RetryableException`] are such that a further attempt at the operation that caused it might be successful. +This is particular useful when running queries in xref:transactions#explicit-transactions[explicit transactions], to know if a failed query is worth re-running. +==== + == Query configuration From 01c2d67f04e3a939fea9c56fd8c8cee57122909d Mon Sep 17 00:00:00 2001 From: Stefano Ottolenghi Date: Wed, 9 Jul 2025 10:42:01 +0200 Subject: [PATCH 09/14] Update query-simple.adoc --- java-manual/modules/ROOT/pages/query-simple.adoc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/java-manual/modules/ROOT/pages/query-simple.adoc b/java-manual/modules/ROOT/pages/query-simple.adoc index 117f1e17..7aed0653 100644 --- a/java-manual/modules/ROOT/pages/query-simple.adoc +++ b/java-manual/modules/ROOT/pages/query-simple.adoc @@ -181,7 +181,7 @@ For those rare use cases, see xref:query-advanced#_dynamic_values_in_property_ke == Error handling A query run may fail for a number of reasons, with different link:https://neo4j.com/docs/api/java-driver/{java-driver-version}/org.neo4j.driver/org/neo4j/driver/exceptions/package-summary.html[exceptions] being raised. -When using `Driver.executableQuery()`, the driver automatically retries to run a failed query, if the failure is deemed to be transient (for example due to temporary server unavailability). +When using `Driver.executableQuery()`, the driver automatically retries to run a failed query if the failure is deemed to be transient (for example due to temporary server unavailability). An exception will be raised if the operation keeps failing after a number of attempts. All exceptions coming from the server are subclasses of link:https://neo4j.com/docs/api/java-driver/{java-driver-version}/org.neo4j.driver/org/neo4j/driver/exceptions/Neo4jException.html[`Neo4jException`]. @@ -198,12 +198,10 @@ try { .execute(); } catch (Neo4jException e) { System.out.printf("Neo4j error code: %s\n", e.code()); - System.out.printf("Exception classification: %s\n", e.rawClassification()); System.out.printf("Exception message: %s\n", e.getMessage()); } /* Neo4j error code: Neo.ClientError.Statement.SyntaxError -Exception classification: Optional[CLIENT_ERROR] Exception message: Invalid input '': expected an expression, '*', 'ALL' or 'DISTINCT' (line 1, column 24 (offset: 23)) "MATCH (p:Person) RETURN" ^ @@ -229,6 +227,7 @@ try { } catch (Neo4jException e) { System.out.printf("Exception GQL status code: %s\n", e.gqlStatus()); System.out.printf("Exception GQL status description: %s\n", e.statusDescription()); + System.out.printf("Exception GQL classification: %s\n", e.rawClassification()); System.out.printf("Exception GQL cause: %s\n", e.gqlCause()); System.out.printf("Exception GQL diagnostic record: %s\n", e.diagnosticRecord()); } @@ -236,6 +235,7 @@ try { /* Exception GQL status code: 42001 Exception GQL status description: error: syntax error or access rule violation - invalid syntax +Exception GQL classification: Optional[CLIENT_ERROR] Exception GQL cause: Optional[org.neo4j.driver.exceptions.Neo4jException: 42I06: Invalid input '', expected: an expression, '*', 'ALL' or 'DISTINCT'.] Exception GQL diagnostic record: {_classification="CLIENT_ERROR", OPERATION_CODE="0", OPERATION="", CURRENT_SCHEMA="/", _position={column: 24, offset: 23, line: 1}} */ From 5c2b6468ddac39e5ff07744930f1bbc127b0e846 Mon Sep 17 00:00:00 2001 From: Stefano Ottolenghi Date: Wed, 9 Jul 2025 10:42:48 +0200 Subject: [PATCH 10/14] Update query-simple.adoc --- java-manual/modules/ROOT/pages/query-simple.adoc | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/java-manual/modules/ROOT/pages/query-simple.adoc b/java-manual/modules/ROOT/pages/query-simple.adoc index 7aed0653..40f0bee2 100644 --- a/java-manual/modules/ROOT/pages/query-simple.adoc +++ b/java-manual/modules/ROOT/pages/query-simple.adoc @@ -190,7 +190,7 @@ You can use an exception's code (retrievable with `.code()`) to stably identify .Basic error handling [source, java, role=nocollapse] ---- -// import import org.neo4j.driver.exceptions.Neo4jException; +// import org.neo4j.driver.exceptions.Neo4jException; try { var result = driver.executableQuery("MATCH (p:Person) RETURN ") @@ -218,7 +218,7 @@ In the example below, the exception's GQL status code is `42001`, but the actual .Usage of `Neo4jException` with GQL-related methods [source, java] ---- -// import import org.neo4j.driver.exceptions.Neo4jException; +// import org.neo4j.driver.exceptions.Neo4jException; try { var result = driver.executableQuery("MATCH (p:Person) RETURN ") @@ -231,7 +231,6 @@ try { System.out.printf("Exception GQL cause: %s\n", e.gqlCause()); System.out.printf("Exception GQL diagnostic record: %s\n", e.diagnosticRecord()); } - /* Exception GQL status code: 42001 Exception GQL status description: error: syntax error or access rule violation - invalid syntax From adb35ef1fd4dfe092e62aa375c850cb949185af0 Mon Sep 17 00:00:00 2001 From: Stefano Ottolenghi Date: Thu, 10 Jul 2025 09:37:34 +0200 Subject: [PATCH 11/14] adjust for new functions on gql errors --- .../modules/ROOT/pages/query-simple.adoc | 70 +++++-------------- 1 file changed, 18 insertions(+), 52 deletions(-) diff --git a/java-manual/modules/ROOT/pages/query-simple.adoc b/java-manual/modules/ROOT/pages/query-simple.adoc index 40f0bee2..632f6cd3 100644 --- a/java-manual/modules/ROOT/pages/query-simple.adoc +++ b/java-manual/modules/ROOT/pages/query-simple.adoc @@ -245,59 +245,25 @@ GQL status codes are particularly helpful when you want your application to beha .Distinguishing between different exception codes [source, java] ---- -package demo; - -import java.util.Map; -import java.util.Optional; -import java.util.Set; - -import org.neo4j.driver.AuthTokens; -import org.neo4j.driver.Driver; -import org.neo4j.driver.GraphDatabase; -import org.neo4j.driver.QueryConfig; -import org.neo4j.driver.exceptions.Neo4jException; - -public class App { - - private static final String dbUri = "{neo4j-database-uri}"; - private static final String dbUser = "{neo4j-username}"; - private static final String dbPassword = "{neo4j-password}"; - - public static void main(String... args) { - try (var driver = GraphDatabase.driver(dbUri, AuthTokens.basic(dbUser, dbPassword))) { - try { - var result = driver.executableQuery("CREATE (p:Person {name: $name}) RETURN ") - .withParameters(Map.of("name", "Frida")) - .withConfig(QueryConfig.builder().withDatabase("neo4j").build()) - .execute(); - } catch (Neo4jException e) { - if (hasGqlStatus(e, "42001")) { - // Neo.ClientError.Statement.SyntaxError - // special handling of syntax error in query - System.out.println(e.getMessage()); - } else if (hasGqlStatus(e, "42NFF")) { - // Neo.ClientError.Security.Forbidden - // special handling of user not having CREATE permissions - System.out.println(e.getMessage()); - } else { - // handling of all other exceptions - System.out.println(e.getMessage()); - } - } - } - } - - public static boolean hasGqlStatus(Neo4jException exception, String... codes) { - var c = Set.of(codes); +// import org.neo4j.driver.exceptions.Neo4jException; - Optional e = Optional.ofNullable(exception); - while (e.isPresent()) { - if (c.contains(e.get().gqlStatus())) { - return true; - } - e = e.get().gqlCause(); - } - return false; +try { + var result = driver.executableQuery("CREATE (p:Person {name: $name}) RETURN ") + .withParameters(Map.of("name", "Frida")) + .withConfig(QueryConfig.builder().withDatabase("neo4j").build()) + .execute(); +} catch (Neo4jException e) { + if (e.containsGqlStatus("42001")) { + // Neo.ClientError.Statement.SyntaxError + // special handling of syntax error in query + System.out.println(e.findByGqlStatus("42001").getMessage()); + } else if (e.containsGqlStatus("42NFF")) { + // Neo.ClientError.Security.Forbidden + // special handling of user not having CREATE permissions + System.out.println(e.findByGqlStatus("42NFF").getMessage()); + } else { + // handling of all other exceptions + System.out.println(e.getMessage()); } } ---- From 62e54d6f7190e9a75a9c6243dab7a4265ef3f9a9 Mon Sep 17 00:00:00 2001 From: Stefano Ottolenghi Date: Thu, 10 Jul 2025 09:48:03 +0200 Subject: [PATCH 12/14] fix test --- java-manual/modules/ROOT/pages/query-simple.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/java-manual/modules/ROOT/pages/query-simple.adoc b/java-manual/modules/ROOT/pages/query-simple.adoc index 632f6cd3..d11c1c01 100644 --- a/java-manual/modules/ROOT/pages/query-simple.adoc +++ b/java-manual/modules/ROOT/pages/query-simple.adoc @@ -256,11 +256,11 @@ try { if (e.containsGqlStatus("42001")) { // Neo.ClientError.Statement.SyntaxError // special handling of syntax error in query - System.out.println(e.findByGqlStatus("42001").getMessage()); + System.out.println(e.findByGqlStatus("42001").get().getMessage()); } else if (e.containsGqlStatus("42NFF")) { // Neo.ClientError.Security.Forbidden // special handling of user not having CREATE permissions - System.out.println(e.findByGqlStatus("42NFF").getMessage()); + System.out.println(e.findByGqlStatus("42NFF").get().getMessage()); } else { // handling of all other exceptions System.out.println(e.getMessage()); From ea23ae70122dbde2102bec216a787d70cc98e71b Mon Sep 17 00:00:00 2001 From: Stefano Ottolenghi Date: Fri, 11 Jul 2025 10:16:40 +0200 Subject: [PATCH 13/14] Update query-simple.adoc --- java-manual/modules/ROOT/pages/query-simple.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java-manual/modules/ROOT/pages/query-simple.adoc b/java-manual/modules/ROOT/pages/query-simple.adoc index d11c1c01..e573edb0 100644 --- a/java-manual/modules/ROOT/pages/query-simple.adoc +++ b/java-manual/modules/ROOT/pages/query-simple.adoc @@ -242,7 +242,7 @@ Exception GQL diagnostic record: {_classification="CLIENT_ERROR", OPERATION_CODE GQL status codes are particularly helpful when you want your application to behave differently depending on the exact error that was raised by the server. -.Distinguishing between different exception codes +.Distinguishing between different error codes [source, java] ---- // import org.neo4j.driver.exceptions.Neo4jException; From a6d6e285f759cb1f2c19feb1811e9fe067fae929 Mon Sep 17 00:00:00 2001 From: Stefano Ottolenghi Date: Fri, 11 Jul 2025 11:06:40 +0200 Subject: [PATCH 14/14] move explicit tx note --- .../modules/ROOT/pages/query-simple.adoc | 9 +-------- .../modules/ROOT/pages/transactions.adoc | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/java-manual/modules/ROOT/pages/query-simple.adoc b/java-manual/modules/ROOT/pages/query-simple.adoc index e573edb0..4dcd3116 100644 --- a/java-manual/modules/ROOT/pages/query-simple.adoc +++ b/java-manual/modules/ROOT/pages/query-simple.adoc @@ -270,18 +270,11 @@ try { [NOTE] ==== -The GQL status code `50N42` is returned when an exception does not have a GQL-status object. +The GQL status code `50N42` is returned when an error does not have a GQL-status object. This can happen if the driver is connected to an older Neo4j server. Don't rely on this status code, as future Neo4j server versions might change it with a more appropriate one. ==== -[TIP] -==== -Transient server errors can be retried without need to alter the original request. -Exceptions implementing link:https://neo4j.com/docs/api/java-driver/{java-driver-version}/org.neo4j.driver/org/neo4j/driver/exceptions/RetryableException.html[`RetryableException`] are such that a further attempt at the operation that caused it might be successful. -This is particular useful when running queries in xref:transactions#explicit-transactions[explicit transactions], to know if a failed query is worth re-running. -==== - == Query configuration diff --git a/java-manual/modules/ROOT/pages/transactions.adoc b/java-manual/modules/ROOT/pages/transactions.adoc index b5db9547..d7e5fb84 100644 --- a/java-manual/modules/ROOT/pages/transactions.adoc +++ b/java-manual/modules/ROOT/pages/transactions.adoc @@ -100,9 +100,9 @@ public class App { // Create & employ 100 people to 10 different organizations public static void main(String... args) { - final String dbUri = "{neo4j-database-uri}"; - final String dbUser = "{neo4j-username}"; - final String dbPassword = "{neo4j-password}"; + final String dbUri = ""; + final String dbUser = ""; + final String dbPassword = ""; try (var driver = GraphDatabase.driver(dbUri, AuthTokens.basic(dbUser, dbPassword))) { try (var session = driver.session(SessionConfig.builder().withDatabase("neo4j").build())) { @@ -224,6 +224,12 @@ try (var session = driver.session(SessionConfig.builder().withDatabase("neo4j"). } ---- +[TIP] +==== +Queries run with `tx.run()` failing due to a transient server error can be retried without need to alter the original request. +Exceptions implementing link:https://neo4j.com/docs/api/java-driver/{java-driver-version}/org.neo4j.driver/org/neo4j/driver/exceptions/RetryableException.html[`RetryableException`] are such that a further attempt at the operation that caused it might be successful. +==== + An explicit transaction can be committed with link:https://neo4j.com/docs/api/java-driver/{java-driver-version}/org.neo4j.driver/org/neo4j/driver/Transaction.html#commit()[`Transaction.commit()`] or rolled back with link:https://neo4j.com/docs/api/java-driver/{java-driver-version}/org.neo4j.driver/org/neo4j/driver/Transaction.html#rollback()[`Transaction.rollback()`]. If no explicit action is taken, the driver will automatically roll back the transaction at the end of its lifetime. @@ -249,9 +255,9 @@ import org.neo4j.driver.Transaction; public class App { public static void main(String... args) { - final String dbUri = "{neo4j-database-uri}"; - final String dbUser = "{neo4j-username}"; - final String dbPassword = "{neo4j-password}"; + final String dbUri = ""; + final String dbUser = ""; + final String dbPassword = ""; try (var driver = GraphDatabase.driver(dbUri, AuthTokens.basic(dbUser, dbPassword))) { driver.verifyConnectivity();