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
}
}
}
public final class MyResolverErrorBuilder implements ResolverErrorBuilder {
@Override
public List<GraphQLError> exceptionToGraphQLError(
Throwable throwable, ErrorReporter.Metadata metadata) {
if (throwable instanceof IllegalArgumentException) {
return List.of(ErrorBuilder.newError(metadata)
.message("Invalid argument")
.extension("errorType", "INVALID_ARGUMENT")
.build());
}
return null;
}
}
You can also use the functional interface syntax for simpler implementations. Shape client messages deliberately; the Java example keeps exception details out of the response:
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)
}
}
}
public final class MyErrorReporter implements ErrorReporter {
@Override
public void reportResolverError(
Throwable exception, String errorMessage, ErrorReporter.Metadata metadata) {
Sentry.captureException(exception, scope -> {
scope.setExtra("fieldName", String.valueOf(metadata.getFieldName()));
scope.setExtra("parentType", String.valueOf(metadata.getParentType()));
scope.setExtra("operationName", String.valueOf(metadata.getOperationName()));
scope.setExtra("path", String.valueOf(metadata.getExecutionPath()));
scope.setExtra("errorMessage", errorMessage);
scope.setExtra("isFrameworkError", String.valueOf(metadata.isFrameworkError()));
scope.setExtra("componentName", String.valueOf(metadata.getComponentName()));
});
}
}
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 |
requestContext |
The deployment-specific context supplied in ExecutionInput |
isSuboperation |
Whether the error occurred inside a suboperation |
componentName |
The component name associated with the field definition |
You can also use the functional interface syntax:
Configuring Error Handlers¶
To use custom error handlers, provide them when building your Viaduct instance:
Both error handlers are optional. If not provided, Viaduct uses sensible defaults:
ResolverErrorBuilder.NOOP- Returnsnull, allowing framework default handlingErrorReporter.NOOP- Does nothing (no external reporting)
From Java, these Kotlin companion properties are available as ResolverErrorBuilder.getNOOP() and ErrorReporter.getNOOP().