Skip to content

Commit 5050934

Browse files
[Java] GQLSTATUS error handling (#472)
Co-authored-by: Stefano Ottolenghi <[email protected]>
1 parent d1780aa commit 5050934

File tree

1 file changed

+79
-9
lines changed

1 file changed

+79
-9
lines changed

java-manual/modules/ROOT/pages/query-simple.adoc

Lines changed: 79 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -182,34 +182,100 @@ For those rare use cases, see xref:query-advanced#_dynamic_values_in_property_ke
182182

183183
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.
184184
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).
185-
An error will be raised if the operation keeps failing after the configured link:https://neo4j.com/docs/api/java-driver/{java-driver-version}/org.neo4j.driver/org/neo4j/driver/Config.ConfigBuilder.html#withMaxTransactionRetryTime(long,java.util.concurrent.TimeUnit)[maximum retry time].
186185

187-
All link:https://neo4j.com/docs/status-codes/current/errors/all-errors/[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`].
186+
An exception will be raised if the operation keeps failing after the configured link:https://neo4j.com/docs/api/java-driver/{java-driver-version}/org.neo4j.driver/org/neo4j/driver/Config.ConfigBuilder.html#withMaxTransactionRetryTime(long,java.util.concurrent.TimeUnit)[maximum retry time].
187+
188+
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`].
188189
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.
189190

190191
.Basic error handling
191-
[source, java]
192+
[source, java, role=nocollapse]
192193
----
193-
// import import org.neo4j.driver.exceptions.Neo4jException;
194+
// import org.neo4j.driver.exceptions.Neo4jException;
194195
195196
try {
196197
var result = driver.executableQuery("MATCH (p:Person) RETURN ")
197-
.withConfig(QueryConfig.builder().withDatabase("<database-name>").build())
198+
.withConfig(QueryConfig.builder().withDatabase("neo4j").build())
198199
.execute();
199200
} catch (Neo4jException e) {
200201
System.out.printf("Neo4j error code: %s\n", e.code());
201-
System.out.printf("Exception classification: %s\n", e.rawClassification());
202202
System.out.printf("Exception message: %s\n", e.getMessage());
203203
}
204204
/*
205205
Neo4j error code: Neo.ClientError.Statement.SyntaxError
206-
Exception classification: Optional[CLIENT_ERROR]
207206
Exception message: Invalid input '': expected an expression, '*', 'ALL' or 'DISTINCT' (line 1, column 24 (offset: 23))
208207
"MATCH (p:Person) RETURN"
209208
^
210209
*/
211210
----
212211

212+
Exception objects also expose errors as GQL-status objects.
213+
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.
214+
215+
The actual _cause_ that triggered an exception is sometimes found in the optional GQL-status object retrievable with `.gqlCause()`, which is itself a `Neo4jException`.
216+
You might need to recursively traverse the cause chain before reaching the root cause of the exception you caught.
217+
In the example below, the exception's GQL status code is `42001`, but the actual source of the error has status code `42I06`.
218+
219+
.Usage of `Neo4jException` with GQL-related methods
220+
[source, java]
221+
----
222+
// import org.neo4j.driver.exceptions.Neo4jException;
223+
224+
try {
225+
var result = driver.executableQuery("MATCH (p:Person) RETURN ")
226+
.withConfig(QueryConfig.builder().withDatabase("neo4j").build())
227+
.execute();
228+
} catch (Neo4jException e) {
229+
System.out.printf("Exception GQL status code: %s\n", e.gqlStatus());
230+
System.out.printf("Exception GQL status description: %s\n", e.statusDescription());
231+
System.out.printf("Exception GQL classification: %s\n", e.rawClassification());
232+
System.out.printf("Exception GQL cause: %s\n", e.gqlCause());
233+
System.out.printf("Exception GQL diagnostic record: %s\n", e.diagnosticRecord());
234+
}
235+
/*
236+
Exception GQL status code: 42001
237+
Exception GQL status description: error: syntax error or access rule violation - invalid syntax
238+
Exception GQL classification: Optional[CLIENT_ERROR]
239+
Exception GQL cause: Optional[org.neo4j.driver.exceptions.Neo4jException: 42I06: Invalid input '', expected: an expression, '*', 'ALL' or 'DISTINCT'.]
240+
Exception GQL diagnostic record: {_classification="CLIENT_ERROR", OPERATION_CODE="0", OPERATION="", CURRENT_SCHEMA="/", _position={column: 24, offset: 23, line: 1}}
241+
*/
242+
----
243+
244+
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.
245+
246+
.Distinguishing between different error codes
247+
[source, java]
248+
----
249+
// import org.neo4j.driver.exceptions.Neo4jException;
250+
251+
try {
252+
var result = driver.executableQuery("CREATE (p:Person {name: $name}) RETURN ")
253+
.withParameters(Map.of("name", "Frida"))
254+
.withConfig(QueryConfig.builder().withDatabase("neo4j").build())
255+
.execute();
256+
} catch (Neo4jException e) {
257+
if (e.containsGqlStatus("42001")) {
258+
// Neo.ClientError.Statement.SyntaxError
259+
// special handling of syntax error in query
260+
System.out.println(e.findByGqlStatus("42001").get().getMessage());
261+
} else if (e.containsGqlStatus("42NFF")) {
262+
// Neo.ClientError.Security.Forbidden
263+
// special handling of user not having CREATE permissions
264+
System.out.println(e.findByGqlStatus("42NFF").get().getMessage());
265+
} else {
266+
// handling of all other exceptions
267+
System.out.println(e.getMessage());
268+
}
269+
}
270+
----
271+
272+
[NOTE]
273+
====
274+
The GQL status code `50N42` is returned when an error does not have a GQL-status object.
275+
This can happen if the driver is connected to an older Neo4j server.
276+
Don't rely on this status code, as future Neo4j server versions might change it with a more appropriate one.
277+
====
278+
213279

214280
== Query configuration
215281

@@ -385,8 +451,12 @@ public class App {
385451
result.summary().resultAvailableAfter(TimeUnit.MILLISECONDS));
386452
387453
} catch (Neo4jException e) {
388-
System.out.println(e.getMessage());
389-
System.exit(1);
454+
if (e.gqlStatus().equals("42NFF")) {
455+
System.out.println("There was a permission issue. Make sure you have correct permissions and try again.");
456+
} else {
457+
System.out.println(e.getMessage());
458+
System.exit(1);
459+
}
390460
}
391461
}
392462
}

0 commit comments

Comments
 (0)