Home > Blockchain >  How to handle custom GraphQL error such that it does not display the exception stack trace in Java
How to handle custom GraphQL error such that it does not display the exception stack trace in Java

Time:03-24

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"
        }
    ]
}
  • Related