0% found this document useful (0 votes)
15 views7 pages

Error Handling in Spring For GraphQL

The document discusses error handling in Spring for GraphQL, highlighting the differences from REST endpoints and the importance of consulting the documentation. It details the implementation of a custom exception handler using DataFetcherExceptionResolverAdapter to manage both custom and built-in exceptions, providing meaningful error messages in GraphQL responses. The author emphasizes the value of thorough documentation review to avoid confusion and improve development efficiency.

Uploaded by

Cobra
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
15 views7 pages

Error Handling in Spring For GraphQL

The document discusses error handling in Spring for GraphQL, highlighting the differences from REST endpoints and the importance of consulting the documentation. It details the implementation of a custom exception handler using DataFetcherExceptionResolverAdapter to manage both custom and built-in exceptions, providing meaningful error messages in GraphQL responses. The author emphasizes the value of thorough documentation review to avoid confusion and improve development efficiency.

Uploaded by

Cobra
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 7

Error handling in Spring for

GraphQL
Recently I wrote some GraphQL endpoints and got a bit blocked when I came to the error handling
mechanism. Usually when writing REST endpoints you either go for a particular @ExceptionHandler
for your controller or you go for the @ControllerAdvice to handle exception handling globally for
multiple controllers. Apparently that is not the case for the GraphQL, there is a completely different
approach for handling errors.

First, the most important thing that I should mention is that I am using
implementation("org.springframework.boot:spring-boot-starter-graphql")

and not the

implementation(“com.graphql-java-kickstart:graphql-spring-boot-starter:14.0.0”)

these are 2 completely different things and this should be kept in mind, during development and
research on different websites.

So what it the problem? Whenever you run a GraphQL query/mutation and your service/facade is
throwing an exception - by default you’re getting this output for the result.

"errors": [

"message": "INTERNAL_ERROR for 2ce1d7be-86f2-da5d-bdba-aac45f4a534f",

"locations": [

"line": 1,

"column": 13

],

"path": [

"deleteCourseById"

],

"extensions": {

"classification": "INTERNAL_ERROR"
}

],

"data": {

"deleteCourseById": null

Meh, that is not intuitive at all! We miss the exception message, right? This needs to be fixed, I want
to be able to provide the exception message and in certain scenarios be able to override the
exception message for some exceptions and display it.

My biggest mistake was to google it straight-way instead of going through the documentation first
https://docs.spring.io/spring-graphql/docs/current/reference/html/ . That led me to a journey of try
and fails as I’ve never seen before, and all of that is because most of the research ecosystem is filled
with QA and tutorials for the com.graphql-java-kickstart:graphql-spring-boot-
starter library or io.leangen.graphql library, and very little is to be found about Spring for
GraphQL. There are lots of valid answers about the error handling either by implementing the
GraphQLError or by implementing a custom GraphQLErrorHandler or by enabling some kind of
property and so on, but none of them work in Spring for GraphQL as it is a completely different
library.

After trying everything out, let’s see what the documentation states https://docs.spring.io/spring-
graphql/docs/current/reference/html/#execution-exceptions :
DataFetcherExceptionResolver is an asynchronous contract. For most
implementations, it would be sufficient to
extend DataFetcherExceptionResolverAdapter and override one of
its resolveToSingleError or resolveToMultipleErrors methods that resolve
exceptions synchronously.

Wow, how simple is that? Lesson learned. Always check documentation first.

In order to demonstrate the error handling in Spring for GraphQL, let’s configure a mini project
about courses and instructors. For this purpose I used Kotlin, but the solution would work in Java as
well. For the sake of conciseness lots of classes won’t be shown here, but you can go ahead and take
a look at the full source code here https://github.com/theFaustus/course-catalog. Here are the DTOs
being used

data class CourseRequest(


@get:NotBlank(message = "must not be blank") val name: String,
@get:NotBlank(message = "must not be blank") val category: String,
val instructor: InstructorRequest
)
data class CourseResponse(
val id: Int?,
val name: String,
val category: String,
val createdAt: String,
val updatedAt: String,
val instructor: InstructorResponse
)

data class InstructorRequest(


@get:NotBlank(message = "must not be blank") val name: String,
)

data class InstructorResponse(


val id: Int?,
val name: String?,
)

And here is their representation in the schema.graphqls


type CourseResponse {
id: ID
name: String
category: String
instructor: InstructorResponse
}

input CourseRequest{
name: String
category: String
instructor: InstructorRequest
}

type InstructorResponse {
id: ID
name: String
}

input InstructorRequest {
name: String
}

Now we have our controller

@Controller
class CourseGraphQLController(val courseFacade: CourseFacade) {

@QueryMapping
fun getCourseById(@Argument id: Int): CourseResponse =
courseFacade.findById(id)

@QueryMapping
fun getAllCourses(): List<CourseResponse> = courseFacade.findAll()

@SchemaMapping(typeName = "CourseResponse", field = "instructor")


fun getInstructor(course: CourseResponse): InstructorResponse =
course.instructor
@MutationMapping
fun deleteCourseById(@Argument id: Int) = courseFacade.deleteById(id)

@MutationMapping
fun createCourse(@Valid @Argument request: CourseRequest):
CourseResponse = courseFacade.save(request)
}

Just for the sake of mentioning, Spring for GraphQL is merely providing support for GraphQL Java in
more opinionated way – annotation based approach. So instead of implementing
GraphQLQueryResolver/ GraphQLMutationResolver we use @QueryMapping and
@MutationMapping alongside with @Argument to resolve the method arguments. Also there is
@SchemaMapping (@QueryMapping/@MutationMapping’s parent) which allows a method to act as
the DataFetcher for a field from the schema mapping.

Okay and here is the schema mapping for the queries/mutations


type Query {
getAllCourses: [CourseResponse]!
getCourseById(id: Int): CourseResponse
}

type Mutation {
deleteCourseById(id: Int): Boolean
createCourse(request: CourseRequest): CourseResponse
}

In order to get a little context about the errors here is my generic NotFoundException thrown from
the service.
class NotFoundException(clazz: KClass<*>, property: String, propertyValue:
String) :
RuntimeException("${clazz.java.simpleName} with $property equal to
[$propertyValue] could not be found!")

So by running the
query { getCourseById(id: -999) {
id
name
instructor {
id
}
}}

I was expecting to get something like Course with id equal to [-999] could not be found! But that was
not the case as we’ve seen at the beginning.

Okay time to fix this. Here is the required subclass according to the documentation.
@Component
class GraphQLExceptionHandler : DataFetcherExceptionResolverAdapter() {
companion object {
private val log: Logger = LoggerFactory.getLogger(this::class.java)
}
override fun resolveToSingleError(e: Throwable, env:
DataFetchingEnvironment): GraphQLError? {
return when (e) {
is NotFoundException -> toGraphQLError(e)
else -> super.resolveToSingleError(e, env)
}
}
private fun toGraphQLError(e: Throwable): GraphQLError? {
log.warn("Exception while handling request: ${e.message}", e)
return
GraphqlErrorBuilder.newError().message(e.message).errorType(ErrorType.DataF
etchingException).build()
}
}

So we extended the DataFetcherExceptionResolverAdapter and overridden the resolveToSingleError


method to treat our exception the correct way. Basically it is a translation of the NotFoundException
to GraphQLError. Now if we run our query again.

"errors": [

"message": "Course with id equal to [-999] could not be found!",

"locations": [],

"extensions": {

"classification": "DataFetchingException"

],

"data": {

"getCourseById": null

Beautiful, isn’t it?

But wait there is more, this here is a custom exception, what about some built-in exceptions like the
ConstraintViolationException which is thrown when the @Valid is invalidated. As you’ve seen my
CourseRequest’s name is annotated with @NotBlank
data class CourseRequest(
@get:NotBlank(message = "must not be blank") val name: String,
@get:NotBlank(message = "must not be blank") val category: String,
val instructor: InstructorRequest
)
What happens when I try to create a Course with an empty name? Like this?
mutation { createCourse(
request: {
name: "",
category: "DEVELOPMENT",
instructor: {
name: "Thomas William"
}
}) {
id
name
}}

Oh God, no… Again that INTERNAL_ERROR message, but no worries, with our
GraphQLExceptionHandler in place it is a matter of adding a new exception to be handled. Also just
for safety, I’ll add the Exception there too, as the times comes new specializations can be added, but
by default for untreated exception the exception message always will be shown. So here is our new
implementation
@Component
class GraphQLExceptionHandler : DataFetcherExceptionResolverAdapter() {
companion object {
private val log: Logger = LoggerFactory.getLogger(this::class.java)
}

override fun resolveToSingleError(e: Throwable, env:


DataFetchingEnvironment): GraphQLError? {
return when (e) {
is NotFoundException -> toGraphQLError(e)
is ConstraintViolationException ->
handleConstraintViolationException(e)
is Exception -> toGraphQLError(e)
else -> super.resolveToSingleError(e, env)
}
}

private fun toGraphQLError(e: Throwable): GraphQLError? {


log.warn("Exception while handling request: ${e.message}", e)
return
GraphqlErrorBuilder.newError().message(e.message).errorType(ErrorType.DataF
etchingException).build()
}

private fun handleConstraintViolationException(e:


ConstraintViolationException): GraphQLError? {
val errorMessages = mutableSetOf<String>()
e.constraintViolations.forEach { errorMessages.add("Field '$
{it.propertyPath}' ${it.message}, but value was [${it.invalidValue}]") }
val message = errorMessages.joinToString("\n")
log.warn("Exception while handling request: $message", e)
return
GraphqlErrorBuilder.newError().message(message).errorType(ErrorType.DataFet
chingException).build()
}
}
As you can see the NotFoundException/Exception will be simply translated to GraphQLError (yes at
the moment, the logic’s the same and NotFoundException may be removed, but I prefer to keep
them separated for future possible changes) and ConstraintViolationException is treated seapartely
by constructing a sensible message.

Now if we run our mutation again. Voila!

"errors": [

"message": "Field 'createCourse.request.name' must not be blank, but value was []",

"locations": [],

"extensions": {

"classification": "DataFetchingException"

],

"data": {

"createCourse": null

Conclusion
In this article we discussed about error handling in Spring for GraphQL and we looked at the
implementation of ErrorHandler that is capable of handling both the custom exception and the built-
in exceptions. And we learned an important lesson: Always check the documentation first!.

That’s all folks, hope that you liked it. In case that you missed it, here is the full project
https://github.com/theFaustus/course-catalog

P.S. here is an unrelated tip for the Kotlin users that are still trying to implement the GraphQLError
and extend the RuntimeException and getting the “Accidental override: The following declarations
have the same JVM signature (getMessage()Ljava/lang/String;):”. The dirty workaround is to have it
implemented in Java and have single java class in a 100% Kotlin project. The elegant workaround is
to extend the newly created GraphqlErrorException specifically created for Kotlin users as per the
opened github issue https://github.com/graphql-java/graphql-java/issues/1690. Lovely, isn’t it?

You might also like