I have created a simple GraphQL endpoint using Spring Boot and I am using DefaultGraphQLErrorHandler() to handle GraphQL errors.
However, when I throw a custom Exception from my application, the error response which GraphQL produces contains Exception stack trace which is giving away too much information. I want to prevent this.
{
"data": {
"CommercialAsset": null
},
"errors": [
{
"message": "Exception while fetching data (/CommercialAsset) : Asset not fround in Data source",
"path": [
"CommercialAsset"
],
"exception": {
"cause": null,
"stackTrace": [
{
"classLoaderName": null,
"moduleName": null,
"moduleVersion": null,
"methodName": "getAssetById",
"fileName": "CommercialAssetDremioRepositoryImpl.java",
"lineNumber": 49,
"className": "com.dell.dremioclient.repository.impl.CommercialAssetDremioRepositoryImpl",
"nativeMethod": false
},
...
{
"classLoaderName": null,
"moduleName": "java.base",
"moduleVersion": "11.0.14",
"methodName": "run",
"fileName": "Thread.java",
"lineNumber": 834,
"className": "java.lang.Thread",
"nativeMethod": false
}
],
"message": "Asset not fround in Data source",
"locations": null,
"errorType": null,
"path": null,
"extensions": null,
"suppressed": [],
"localizedMessage": "Asset not fround in Data source"
},
"locations": [
{
"line": 2,
"column": 5,
"sourceName": null
}
],
"extensions": null,
"errorType": "DataFetchingException"
}
]
}
GraphQL dependency versions that I am using :
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-spring-boot-starter</artifactId>
<version>5.0.2</version>
</dependency>
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java-tools</artifactId>
<version>5.2.4</version>
</dependency>
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphiql-spring-boot-starter</artifactId>
<version>5.0.2</version>
</dependency>
Is there any way I can do it using Custom error handler? Something like this -
public class CustomGraphqlErrorHandler implements GraphQLErrorHandler {
@Override
public List<GraphQLError> processErrors(List<GraphQLError> errors) {
List<GraphQLError> errorList = new ArrayList<>();
errors.stream()
.forEach( e -> {
if(this.isServerError(e)) {
GraphqlDremioClientException gexp = new GraphqlDremioClientException(e.getMessage());
gexp.setStackTrace(null); /* This causes failure Bad POST request: parsing failed
java.lang.NullPointerException: null
at java.base/java.lang.Throwable.setStackTrace(Throwable.java:865) */
errorList.add(gexp);
} else {
errorList.add(e);
}
});
return errorList;
}
private boolean isServerError(GraphQLError error) {
return (error instanceof ExceptionWhileDataFetching || error instanceof Throwable);
}
@Override
public boolean errorsPresent(List<GraphQLError> errors) {
return !CollectionUtils.isEmpty(errors);
}
}
CodePudding user response:
I wanted to prevent GraphQL to show stack trace in the error response. One simple solution to it was to add a custom GraphQL error handler to handle the exceptions thrown from my services. I then, created a custom Exception class which could enable or disable stack trace during construction.
Custom Exception class:
public class GraphqlDremioClientException extends RuntimeException implements GraphQLError {
private static final long serialVersionUID = 1L;
private final String message;
private boolean noStacktrace = false;
@Override
public String getMessage() {
return message;
}
/* other constructors */
public GraphqlDremioClientException(String message, boolean noStacktrace) {
super(message, null, false, noStacktrace);
this.noStacktrace = noStacktrace;
this.message = message;
}
public GraphqlDremioClientException(String message, Exception ex) {
super();
this.message = message;
}
@Override
public List<SourceLocation> getLocations() {
return null;
}
@Override
public ErrorType getErrorType() {
return null;
}
}
Custome GraphQL error handler:
@Slf4j
@Component
public class CustomGraphqlErrorHandler implements GraphQLErrorHandler {
@Override
public List<GraphQLError> processErrors(List<GraphQLError> list) {
return list.stream().map(this::getNested).collect(Collectors.toList());
}
private GraphQLError getNested(GraphQLError error) {
log.error(error.getMessage(), error);
if (error instanceof ExceptionWhileDataFetching) {
ExceptionWhileDataFetching exceptionError = (ExceptionWhileDataFetching) error;
if (exceptionError.getException() instanceof GraphQLError) {
return new GraphqlDremioClientException(exceptionError.getMessage(), false);
}
}
return error;
}
}
The error response now:
{
"data": {
"CommercialAsset": null
},
"errors": [
{
"cause": null,
"stackTrace": [],
"message": "Exception while fetching data (/CommercialAsset) : Asset not fround in Data source",
"noStacktrace": false,
"locations": null,
"errorType": null,
"path": null,
"extensions": null,
"suppressed": [],
"localizedMessage": "Exception while fetching data (/CommercialAsset) : Asset not fround in Data source"
}
]
}