Error Handling
Data Fetcher Error Handling¶
Viaduct provides two extension points for customizing error handling in resolvers. Both are optional for service architects.
ResolverErrorBuilder¶
When a resolver throws an exception, Viaduct will catch it and return it
as a GraphQL error. As a service architect, you can customize
resolver exception handling by implementing your own ResolverErrorBuilder.
ResolverErrorBuilder is a functional interface with a single method,
exceptionToGraphQLError. This method takes the thrown exception and
error metadata, and produces a list of GraphQLError
objects. Return null to indicate that your builder does not handle this
exception type, allowing the framework to use default handling.
import viaduct.service.api.GraphQLError
import viaduct.service.api.spi.ErrorBuilder
import viaduct.service.api.spi.ErrorReporter
import viaduct.service.api.spi.ResolverErrorBuilder
class MyResolverErrorBuilder : ResolverErrorBuilder {
override fun exceptionToGraphQLError(
throwable: Throwable,
errorMetadata: ErrorReporter.Metadata
): List<GraphQLError>? {
return when (throwable) {
is MyCustomException -> listOf(
ErrorBuilder.newError(errorMetadata)
.message("A custom error occurred: ${throwable.customMessage}")
.extension("errorType", "CUSTOM_ERROR")
.build()
)
is IllegalArgumentException -> listOf(
ErrorBuilder.newError(errorMetadata)
.message("Invalid argument: ${throwable.message}")
.extension("errorType", "INVALID_ARGUMENT")
.build()
)
else -> null // Return null for unhandled exceptions
}
}
}
You can also use the functional interface syntax for simpler implementations:
val myErrorBuilder = ResolverErrorBuilder { throwable, metadata ->
listOf(
ErrorBuilder.newError(metadata)
.message("Error: ${throwable.message}")
.extension("errorType", "INTERNAL_ERROR")
.build()
)
}
The ErrorBuilder helper class automatically
populates the error's path and locations from the metadata, so you only
need to set the message and any custom extensions.
ErrorReporter¶
In addition to returning errors in ExecutionResult, Viaduct also
allows you to configure an error reporter called from within the engine.
ErrorReporter is a functional interface
with a single method, reportResolverError. This method is called whenever
a resolver throws an exception and allows you to log the error or send it
to an external monitoring system. This interface does not affect error
reporting to clients or handling within the Viaduct engine.
For instance, if you wanted to emit exceptions to Sentry, you could implement the interface like this:
import viaduct.service.api.spi.ErrorReporter
class MyErrorReporter : ErrorReporter {
override fun reportResolverError(
exception: Throwable,
errorMessage: String,
metadata: ErrorReporter.Metadata
) {
Sentry.captureException(exception) {
it.setExtra("fieldName", metadata.fieldName)
it.setExtra("parentType", metadata.parentType)
it.setExtra("operationName", metadata.operationName)
it.setExtra("path", metadata.executionPath?.joinToString("/"))
it.setExtra("errorMessage", errorMessage)
it.setExtra("isFrameworkError", metadata.isFrameworkError)
it.setExtra("componentName", metadata.componentName)
}
}
}
The Metadata class provides
rich context about the error:
| Field | Description |
|---|---|
fieldName |
The name of the field where the error occurred |
parentType |
The type of the parent object |
operationName |
The name of the GraphQL operation |
executionPath |
The execution path to the field (e.g., ["user", "profile", 0]) |
sourceLocation |
Source location in the GraphQL query document |
isFrameworkError |
Whether the error is a framework error or tenant error |
resolvers |
List of resolver class names involved in the error |
source |
The source object being resolved (parent object) |
context |
The GraphQL context containing request-level data |
localContext |
The local context for field-specific data |
componentName |
The component name associated with the field definition |
You can also use the functional interface syntax:
val myErrorReporter = ErrorReporter { exception, errorMessage, metadata ->
logger.error(
"Error in field ${metadata.fieldName}: $errorMessage",
exception
)
}
Configuring Error Handlers¶
To use custom error handlers, provide them when building your Viaduct instance:
val viaduct = ViaductBuilder()
.withMeterRegistry(meterRegistry)
.withDataFetcherErrorBuilder(MyResolverErrorBuilder())
.withResolverErrorReporter(MyErrorReporter())
.withTenantAPIBootstrapperBuilder(myBootstrapper)
.build()
Both error handlers are optional. If not provided, Viaduct uses sensible defaults:
- ResolverErrorBuilder.NOOP - Returns null, allowing framework default handling
- ErrorReporter.NOOP - Does nothing (no external reporting)