DEV Community

Cover image for Understanding GraphQL Error Handling mechanisms in Spring-boot
imphilippesimo
imphilippesimo

Posted on • Originally published at Medium

Understanding GraphQL Error Handling mechanisms in Spring-boot

Overview

This article is the second part of the series GraphQL, from Theory to real-world with Spring boot.

Today’s episode is about setting up your Spring-boot project to host your first GraphQL API entry points then call them using the web-based tool GraphiQL.

It also aims to bring an in-depth understanding of how the Graphql Java Implementation (graphql-java) deals with error handling and especially how to rely on that, by using the GraphQL Spring Framework boot starter, to handle efficiently errors within our app, as this is an aspect that is often overlooked by early graphql adopters.

TL;DR

Check the corresponding video out:

Before we start, let’s introduce some context.

GraphQL quick summary

GraphQL is a specification that defines not only a new query language for APIs but also how data is returned in response to these queries.
It is an alternative to using Restful APIs in the sense that it solves some of their drawbacks with new features such as these described ones:

  • The client has full control over the data returned to him, he receives only what he needs, no more, no less. No unnecessary data is therefore loaded onto the network. No over-etching.

  • The client can request several resources at once in a single request, no need to send multiple requests. Ideal for slow mobile connections.

  • The server defines what is possible or not with the notion of types, to force the client to request only what is possible.

With great power comes great responsibilities

According to the features mentioned above, the client must be highly considered when setting up such an API.

It is more than important to return a response format that is consistent and predictable across several scenarios, to make it easier for the customer to consume the response.

According to the specification, a GraphQL response should follow this pattern:

A GraphQL response might always have the HTTP status 200 OK.

HTTP error codes are not relevant when using GraphQL because,
If a request fails, the JSON payload of the server’s response will contain a root field called “errors” that contains precise information about the problems that occurred on the server-side.

Moreover, since GraphQL allows for multiple operations to be sent in the same request, well, it’s possible that a request fails partially and returns actual data and errors.

The response body could contain the following fields:
“data”: where the data resulting from the operation reside.
“errors”: where all the errors are filled in.
“extensions”: for additional information about the response

The "errors" field, should contain the following fields :
"message": a key describing the error
"locations": a list of graph coordinates where the errors have occurred.
"path": a key describing the absolute path from the root of the graph to the field where an error has occurred.
"extensions" key for the metadata related to the errors.

You can learn more about GraphQL response format by referring to the specification part that deals with this topic.

As far as we are concerned, we will just focus on the "errors" key.

By relying on the graphql-java and graphql-spring-boot within a spring-boot application, we will dissect the underlying mechanism of errors handling within these libraries to let you know what are your possibilities when it comes to customizing some of the default error handling mechanisms.

Application Set up

We will use a simple spring-boot sample-graphql-error-handling that will expose two endpoints:

createUser(username:string, password:string) that fails if a user is already registered with the same username.

getUser(username:string,password:string) that fails if no user is existing for the given username.

Generate the project using Spring Initializr and add the following dependencies to the pom.xml file:

Minimal maven dependencies to enable graphql implementation within a spring boot app

Our User model Class: User.java

The User model class

The Schema definition file: src/main/resources/UserQL.graphqls

User.graphqls

The schema speaks for itself, we will have a read request getUser which will return a User corresponding to the username and password given as parameters, and a write request which will register a User with its credentials as parameters.

We will use a service class, acting as our business logic that will maintain a list of in-memory users. This service will be injected into our resolvers later.

UserService.java

The createUser method will return a UserAlreadyExistsException if a user already exists.

Similarly, the getUsermethod will return a UserNotFoundException in case no user is found.

Next, we have the code of our exceptions:

UserAlreadyExistsException.java
UserNotFoundException.java

We will rely on these customized exceptions to return relevant information to the customer.

Finally, the resolvers code:

UserQuery.java
UserMutation.java

Now that the project is set up, we can start sending the requests and analyze the errors.

Run the spring boot app and launch the graphiql tool within the browser with: http://localhost:8080/graphiql if you are running on the local machine with the default spring boot app port.

The generic error (case#1)

Within the graphiql tool, let's start by creating a user:

mutation {
  createUser(username: "johndoe", password: "pwd") {
    id
  }
}

Everything is OK and the user is indeed created as confirmed by the answer:

Alt Text

If we try to get a non-existing user:

mutation {
  createUser(username: "johndoe", password: "def") {
    id
  }
}

Alt Text

Not only are the details as defined by the specification missing, but the error message is also inconsistent, as it indicates a server-side error yet it is a client-side error.

Also, if we try to create a user that is already existing:

mutation {
  createUser(username: "johndoe", password: "pwd") {
    id
  }
}

Alt Text

Let’s consider what just happened as CASE #1

For CASE #2 : We need to customize the information according to the error that occurred while giving the client additional information about what happened in the backend so that he can react accordingly.

Customizing errors (case#2)

We've already taken a step forward by defining and throwing problem-related customized exceptions. However, these exceptions need to be transformed into GraphQL errors that respect the standard format before being sent to the client.

graphql-java provides the GraphQLError interface representing a GraphQL standard error that our exceptions will implement.

Our exceptions become:

UserAlreadyExistsException.java as GraphQLError
UserNotFoundException.java as GraphQLError with extra data

Overriding getLocations() and getErrorType() is mandatory.

We will just return nullin these methods because they are ignored by the default error handler anyway.
We will see why later.

In the case of wrong identification data, we want to add an “invalidField” field under the key “extensions” of the error to indicate to the customer which field is in error and needs to be corrected.

This is why we redefine the getExtensions() method in UserNotFoundException

Don’t forget to change the instantiation of the exception in UserService.java.

throw new UserNotFoundException(We were unable to find a user with the provided credentials, username);

Let’s start the application again and run the same test scenario with graphiql to compare the results.

Let’s start by creating a user, the answer is, unsurprisingly, this one:

Alt Text

Then, if you try to retrieve a user that doesn’t exist, the request results in the following error:

Alt Text

Note the invalid field present in the extensions block of the result.

Also, If we try to create a user with an existing username, the response is as follows:

Alt Text

Let’s dissect what just happened in both cases #1 and #2:

When we throw an exception while fetching data:

1. The exception is handled by default by the SimpleDataFetcherExceptionHandler

The handler wraps 4 things: the thrown custom exception (the RuntimeException), the exception message, the error locations, and the extensions, within an ExceptionWhileDataFetchingerror (which is an implementation of the GraphQLErrorinterface).
Then, the ExceptionWhileDataFetching is added to the list of errors of the query result. As we can see in the following SimpleDataFetcherExceptionHandler code.

SimpleDataFetcherExceptionHandler.java

At line 10, a new ExceptionWhileDataFetching is constructed with the error path, our exception, and the sourceLocation.

Remember I told you the methods getLocations() and getErrorType() of our exception were ignored?
Well here’s why:

ExceptionWhileDataFetching.java

In this code snippet from the ExceptionWhileDataFetching class, only the methods getMessage() and getExtensions() are called, the path and the sourceLocation are already provided upon class instantiation.

2. The DefaultGraphQLErrorHandler

After the SimpleDataFetcherExceptionHandler process, another handler, defined by the graphql-spring-boot library comes into action to handle the returned list of errors.
He is the GraphQLErrorHandler: the default implementation is the DefaultGraphQLErrorHandler

These 2 steps can be illustrated as follows:

Alt Text

The Simple DataFetcherExceptionHandler process

Alt Text

The DefaultGraphQLErrorHandler process

In case#1: Our custom exceptions were not instances of GraphQLErrors, the DefaultGraphQLErrorHandler considered them as internal server errors, containing details that should not reach the client. They were filtered and converted to GenericGraphQLError as illustrated.

In case#2: Our custom exceptions were instances of GraphQLErrors, hence they were not reduced to Generic graphQL errors.

This behavior is described in the DefaultGraphQLErrorHandler::processErrors() method.

DefaultGraphQLErrorHandler::processErrors()

Now we clearly understand why we get such a useful error response and a valid format for case#2.

If the SimpleDataFetcherExceptionHandler behavior doesn’t fit your use case, you can create a CustomDataFetcherExceptionHandlerimplementing DataFectcherExceptionHandler, then define your own logic and throw something different than an ExceptionWhileDataFetching GraphQLError.

However, this is not something I will advise as you can find yourself breaking the rules of the GraphQL Specification by sending non-standard error messages. Unless you really understand what you are trying to implement.

Besides, customizing this behavior implies modifying the query execution strategy. As graphql-spring-boot integration is relying on graphql-java to implement the query execution strategy, you will have to deal with them, which, IMHO, does not worth it, as they already implement a pretty good behavior.

Similarly, If the DefaultGraphQLErrorHandlerbehavior doesn’t fit your use case, you can create a CustomGraphQLErrorHandler implementing GraphQLErrorHandler, then define your own logic and handle GraphQLErrors differently.

Let’s supposed we are not satisfied by the string: Exception while fetching data (/xxxx) which is automatically appended to the error message sent to the user, and we just want our original message in the error, without any fancies. We will send our unwrapped original custom exception to the final user by defining a CustomGraphQLErrorHandler.

Sending unwrapped exception

We create the CustomGraphErrorHandler as follows:

CustomGraphQLErrorHandler.java

We override the processErrors method by telling the handler to: first check if the exception is an ExceptionWhileDataFetching then extract our original exception( UserNotFoundException, UserExistsException …) and return it. Otherwise, the handler will just return the received exception as it is.

If we try to find a non-existing user, we get the following error:

As our exceptions are sent back to the user unwrapped, the message is now concise and precise.

Conclusion

In this article, we were focusing two notions: 

Setting up your Spring-boot project to host your first GraphQL API entry points, 

Understanding how the graphql-java implementation and graphql-spring-boot handle errors triggered within an application. 

We have covered the following sections:

  • Setting up a sample project
  • Understanding the standard Graphql error response format
  • Diving into the mechanisms behind generic graphql errors and more tailored ones
  • Adding additional custom information to the error
  • Getting to know the possibilities of further error customizations.

The Github repo about this article is available here.

In the next article, we will see how to Secure our APIs with pretty simple steps.

Thanks for reading, feedback is a gift 🎁.

Top comments (1)

Collapse
 
iksploetzwich profile image
Robin Plötzwich

This helped a lot, thanks!