jakarta-eegraphqlopen-liberty

Error handling in Microprofile GraphQL on Openliberty


I am struggling with finding the right approach to handle exceptions being thrown in my service layer so that the GraphQL response includes a meaningful error message. I know that server errors should not contain any message but here we are talking client errors.

GPT suggests using an ExceptionMapper from JaxRS but that doesn't work:

@GraphQLApi
@RegisterProvider(MyExceptionMapper.class)
public class MembersGraphQLApi {

    @Query("members")
    public List<Member> members(String keyword) throws UserException {
        throw new UserException("This is a test exception");
    }
}

and

@Provider
public class MyExceptionMapper implements ExceptionMapper<UserException> {

    @Override
    public Response toResponse(UserException exception) {
        // just some dummy code to make the response very different
        // to the generated response
        return Response
                .status(400)
                .entity(exception.getMessage())
                .build();
    }

}

This is the response that is generated by the server automatically, which I want to be different:

{
    "errors": [
        {
            "message": "Server Error",
            "locations": [
                {
                    "line": 2,
                    "column": 2
                }
            ],
            "path": [
                "members"
            ],
            "extensions": {
                "exception": "de.codevibe.jeelab.UserException",
                "classification": "DataFetchingException",
                "code": "user"
            }
        }
    ],
    "data": {
        "members": null
    }
}

My only solution so far is to catch every business exception and rethrow as some GraphQLException exception subtype but that feels stupid doing all that boilerplate handling in each query method.

Basically what I want is something like a Spring Boot Controller-Advice.


Solution

  • Yeah, GPT hallucinates that it would be nice to use the same exception mapping mechanism as JAX-RS. But that's actually not even necessary, as the format for error responses is specified in the GraphQL spec, which is not true for REST; so JAX-RS must be much more powerful, while GraphQL is simpler.

    The terms client error vs. server error is not generally well defined. In the MP GraphQL Spec, a client error is defined as the client sending a technically invalid request, but the UserException you want to throw may be the result of a deep validation, e.g. some data in the database does not exist. According to the above specification, this is a server error.

    By default, the message returned for unchecked exceptions is (by default) always "Server Error", so no details about the actual Java exception are leaked to the client. But for checked exceptions, the message is included. So your UserException seems to be unchecked, and that's fine, too:

    You can put the fully qualified name of your exception class de.codevibe.jeelab.UserException into a MP Config mp.graphql.showErrorMessage (Note that the deprecated name for this option is mp.graphql.exceptionsWhiteList). If you have multiple exceptions, you can list them with a comma as delimiter, or even better: you can create a common superclass of all the exceptions that the client should understand what they did wrong, and configure that.

    That's the message; while the code in the extensions is derived from the class name, and there is more to say about that.