Client mode usage description

Introduction

The client mode makes it easy for a Java GraphQL client-side application, to execute queries/mutations/subscriptions against a GraphQL server. The graphql-maven-plugin generates all the necessary code, so that a Java application can call a GraphQL server by simply calling the relevant Java method.

The plugin manages two kinds of request:

  • The Full request: it's actually a standard GraphQL request, that you can test within graphiql
  • The Partial request: you can call a java method that executes one of the queries/mutations/subscriptions defined in the schema. This java method accepts one parameter for each parameter of this query/mutation/subscription.

It manages two ways of executing the request:

  • The direct execution: you call the generated method with the GraphQL request (partial or full), and you receive the result into Java objects. This is simpler, but slower: for technical reasons, the plugin has two analyze the content of the request. And it will do that at each execution. The main reason for that is to allow proper deserialization of GraphQL interfaces and unions: the __typename is injected into the query, for all returned object, union and interface types.
  • The recommended prepared execution:
    • A GraphQLRequest object is created by the application. This allows to analyze the request only once. If you create these GraphQLRequest at application startup, then the syntax control is done once for every requests at startup. This avoids to have errors occurring later, during the app execution.
    • Each GraphQL request execution is executed from this object.
    • Note: the GraphQLRequest object has been created in the 1.6 release. The prepared object was before stored into a ObjectResponse. This ObjectResponse has been maintained when used with the withQueryResponseDef Builder method, and the code that uses will continue to work. Support for other Builder method has been removed. There is no plan yet to remove the ObjectResponse object and the withQueryResponseDef Builder method. But they should be avoided in new code.

Both kinds of requests, and both modes of execution allows to use bind parameters into your queries/mutations/subscriptions (see below for more information on that)

Full requests versus Partial requests

When the plugin generates the query/mutation/subscription java classes (one for each), it generates two families of methods: Full requests, and Partial requests, as introduced on the top of this page.

In either case, you can call query and mutation in the exact same way. You'll only find queries, in the samples below. But mutations work the same.

Full requests

Full requests are only valid for queries and mutations. Subscriptions work only with Partial Request. See the Client Subscription page.

For each query/mutation/subscription in the GraphQL schema, a Java class is generated.

The exec and execWithBindValues methods allows you to execute full GraphQL queries, that is: the full GraphQL request, as it would work in the graphiql interface. So you can test your request in graphiql, then parse the tested request into you code, as a Full Request (see below for sample).

This is interesting when:

  • You want to execute several queries into one call toward the server
  • You want to add directives to the query/mutation itself
  • You want to use GraphQL global fragments into your query (by global, we mean not inline fragment)

You'll find samples in the sample projects, in the test part of the client samples.

To use full request, you need to (sample follows):

  • Configure the GraphQLRequest
    • To do this, you must create a GraphQLConfiguration. This configuration is essentially defining the GraphQL endpoint
    • If you have only one GraphQL server, you can use the default GraphQLRequest, by calling its static setStaticConfiguration(GraphQLConfiguration) method
    • If you work with more than one GraphQL server, you can use the setStaticConfiguration(GraphQLConfiguration) to set a default configuration, and call the setInstanceConfiguration(GraphQLConfiguration) method for instance of GraphQLRequest that would call another server.
  • Create on GraphQLRequest with your GraphQL query
  • Then execute one or several time the query, possibly with different bind parameter values.
  • Retrieves from the query/mutation response, the field the contains the structure for the query(ies)/mutation(s) you called. All the server response is stored in POJOs that have been generated from the GraphQL schema, including object types, input types, enums, interfaces, unions (as Java interfaces)...

To execute the request, you must use the execQuery/execMutation method of your GraphQLRequest instance, to get the proper response. This response is the query/mutation response type. So, to get the real result from the server, you must call the getter that is link to your request, getBoards() in the above sample.

GraphQLRequest request;

void setup() {
                // If you have one GraphQL endpoint, use the static configuration.
                // The classname of the Query or Mutation is the type name, as defined in the GraphQL schema
                GraphQLRequest.setStaticConfiguration(new GraphQLConfiguration(Main.GRAPHQL_ENDPOINT_URL));
                
                // Create the GraphQL request
                request = new GraphQLRequest("" //
                                + "fragment topic on Topic {title posts(since: &sinceParam, memberId: ?memberIdParam, memberName: \"?\"){id} author{id}}\r"
                                + "query{boards{id name topics {id ...topic}}}");
}

void exec(Date since) {
        // This sample :
        // - gives a value for the mandatory (as it starts with &) sinceParam bind parameter,
        // - gives no value for the optional (as it starts with ?) memberIdParam bind parameter.
        // So only the since bind parameter will be sent to the server. 
        List<Board> boards = request.execQuery("sinceParam", since).getBoards();

        // Do something with boards
        ...
}

In the GraphQL query, you can use:

  • Fragments (inline or not)
  • Directives
  • Union, or interface types
  • input parameters for query/mutation and field input parameters, or in directives arguments. The given value for these input parameters may be:
    • Hard coded value in your GraphQL query. That is: the literal value for the input parameter, like for the memberName parameter in the above sample.
    • An optional bind parameter, if it's a valid identifier, prefixed with ?, like the memberIdParam parameter, here above. In this case, if you don't provide a value for this parameter at execution time, the parameter is not sent to the server. Of course, if this parameter is mandatory in your GraphQL schema, you'll get an error from the GraphQL server.
    • A mandatory bind parameter, if it's a valid identifier, prefixed with &, like the sinceParam parameter, here above. In this case, if you don't provide a value for this parameter at execution time, the plugin throws a GraphQLRequestExecutionException at execution time, as it expects a value for this parameter. A mandatory parameter in your query may be an optional one in the GraphQL schema: it's up to your use case to define if this parameter is mandatory or not.

Please note that the bind parameters can be provided as a map, where the key is the parameter name, by using the xxxWithBindValues(GraphQLRequest, [xxx], Map), where [xxx] is the list of values for the query/mutation.

The previous sample then becomes:

GraphQLRequest request;

void setup() {
                // If you have one GraphQL endpoint, use the static configuration.
                // The classname of the Query or Mutation is the type name, as defined in the GraphQL schema            
                GraphQLRequest.setStaticConfiguration(new GraphQLConfiguration(Main.GRAPHQL_ENDPOINT_URL));
                
                // Create the GraphQL request
                request = new GraphQLRequest("" //
                                + "fragment topic on Topic {title posts(since: &sinceParam, memberId: ?memberIdParam, memberName: \"?\"){id} author{id}}\r"
                                + "query{boards{id name topics {id ...topic}}}");
}

void exec(Date since, String memberName, String memberId) {
        Map<String, Object> params = new HashMap<>();
        params.put("sinceParam", since);
        params.put("memberName", memberName);
        params.put("memberId", memberId);
        
        // This sample :
        // - gives a value for the mandatory (as it starts with &) sinceParam bind parameter,
        // - gives no value for the optional (as it starts with ?) memberIdParam bind parameter.
        // So only the since bind parameter will be sent to the server. 
        List<Board> boards = request.execQuery(params).getBoards();

        // Do something with boards
        ...
}

Partial requests

For each query/mutation/subscription class, the plugin also generates XxxxEXecutor classes, where Xxxx is the query/mutation/subscription GraphQL type name. These classes contain Yyyy and YyyyWithBindValues methods, where Yyyy is successively each query/mutation/subscription defined in this query/mutation/subscription object.

As subscriptions work differently, they are documented in the Client Subscription page.

These methods are easier to use as:

  • The query/mutation/subscription parameters (as defined in the GraphQL schema) become parameters of the relevant generated java methods. So you don't need to define and map bind parameters for them.
    • But you can still use bind parameters for input parameters of the fields you request, of course
  • The methods return directly the response for the query/mutation/subscription: you don't need to call a getter to retrieve it. The returned data is stored in the POJOs that have been generated from the GraphQL schema. This includes the object types, input types, enums, interfaces, unions (as Java interfaces)...

Below is a sample of the client code:

QueryTypeExecutor queryExecutor;
GraphQLRequest topicsRequest;

void setup() {
                // Instanciate a Query executor, with the relevant GraphQL endpoint
                queryExecutor = new QueryTypeExecutor("https://your.server/graphql");
                                
                // Create the GraphQL request
                // This prepares a GraphQ Request that will execute: topics{id date author{name email alias id type} nbPosts}
                topicsRequest = queryExecutor.getTopicsGraphQLRequest("{id date author{name email alias id type} nbPosts}");
}

void exec() {
        // The topic query accepts one parameter. If you don't want to provide it, you can give it the null value
        List<Topic> topics = queryExecutor.topics(topicsRequest, "a board name");

        // Do something with topics
        ...
}

This method needs less code, in your application.

Directives can be defined for the requested fields, or their parameters. If you need to define directives at the query/mutation/subscription level, you need to execute a Full Request (see here above).

The bind parameters works as specified for the Full queries.

Of course, you can use input parameters for field or directives.

Below is a sample of a request, with bind parameters:

QueryTypeExecutor queryExecutor = new QueryTypeExecutor("https://your.server/graphql");
GraphQLRequest topicsRequest;

void setup() {
                // ?memberId is an optional bind parameter, of name "memberId"
                // ?memberName is an optional bind parameter, of name "memberName"
                // ?sinceParam is an optional bind parameter, of name "sinceParam": you must provide it at execution time, or
                // a GraphQLRequestExecutionException will be thrown.
                topicsRequest = queryExecutor.getTopicsGraphQLRequest("{id date author{name email alias id type} nbPosts "
                        + "posts(memberId:?memberId, memberName: ?memberName, since: &sinceParam){id date author{name email alias}}}");
}

void exec(Date since, String memberName, String memberId) {
        // The topic query, as defined in the GraphQL schema, has one parameter: the board name.
        // This parameter is a paremeter of the topics method
        // You can then as many couples ("bindParameterName", bindParameterValue) that you want, in any order
        List<Topic> topics = queryExecutor.topics(topicsRequest, "a board name","sinceParam", since, "memberName", memberName, "memberId", memberId);

        // Do something with topics
        ...
}

Please note that the bind parameters can be provided as a map, where the key is the parameter name, by using the xxxWithBindValues(GraphQLRequest, [xxx], Map), where [xxx] is the list of values for the query/mutation/subscription.

The previous sample then becomes:

QueryTypeExecutor queryExecutor = new QueryTypeExecutor("https://your.server/graphql");
GraphQLRequest topicsRequest;

void setup() {
                topicsRequest = queryExecutor.getTopicsGraphQLRequest("{id date author{name email alias id type} nbPosts "
                        + "posts(memberId:?memberId, memberName: ?memberName, since: &sinceParam){id date author{name email alias}}}");
}

void exec(Date since, String memberName, String memberId) {
        Map<String, Object> params = new HashMap<>();
        params.put("sinceParam", since);
        params.put("memberName", memberName);
        params.put("memberId", memberId);
        
        // The topic query, as defined in the GraphQL schema, has one parameter: the board name
        // This parameter is a paremeter of the topics method
        List<Topic> topics = queryExecutor.topicsWithBindValues(topicsRequest, "a board name", params);

        // Do something with topics
        ...
}

Use direct queries

If you don't want to store the GraphQLRequest, you can avoid that, by using direct queries. But please note that there is an overhead, as the query must be analyzed at runtime by the plugin to manage bind parameters, and add the __typename GraphQL introspection attribute where it is missing, to insure proper java deserialization of the response.

So using direct queries generates an overhead at each execution, whereas with prepared queries, the request analysis is done only once.

Direct queries are available only for partial queries. The input parameters for fields and directive are provided the same way as for full queries:

  • With a list of couples ("bindParameterName", bindParameterValue)
  • Or with a map containing this parameter values, where the key is the bindParameterName, and the value is the bind parameter value.

    Here is a sample with the bind parameters value given as method parameters:

    QueryTypeExecutor queryExecutor = new QueryTypeExecutor("https://your.server/graphql");
    
    void exec(Date since, String memberName, String memberId) {
            
            // The topic query accept one parameter.
            // You can then as many couples ("bindParameterName", bindParameterValue) that you want.
            List<Topic> topics = queryExecutor.topicsWithBindValues("{id date author{name email alias id type} nbPosts "
                            + "posts(memberId:?memberId, memberName: ?memberName, since: &sinceParam){id date author{name email alias}}}",
                            // One parameter for this query, in the GraphQL schema 
                            "a board name", 
                            // Then the parameters name and value: 
                            "sinceParam", since,
                            "memberName", memberName,
                            "memberId", memberId,
                            );
    
            // Do something with topics
            ...
    }
    

    Here is a sample with the bind parameters value given as a map:

    QueryTypeExecutor queryExecutor = new QueryTypeExecutor("https://your.server/graphql");
    
    void exec(Date since, String memberName, String memberId) {
            Map<String, Object> params = new HashMap<>();
            params.put("sinceParam", since);
            params.put("memberName", memberName);
            params.put("memberId", memberId);
            
            // The topic query accept one parameter.
            // You can then as many couples ("bindParameterName", bindParameterValue) that you want.
            List<Topic> topics = queryExecutor.topicsWithBindValues("{id date author{name email alias id type} nbPosts "
                            + "posts(memberId:?memberId, memberName: ?memberName, since: &sinceParam){id date author{name email alias}}}", 
                            // One parameter for this query, in the GraphQL schema 
                            "a board name",
                            // The bind values are in this map 
                            params);
    
            // Do something with topics
            ...
    }