Creating a Spring client app

Introduction

Since the 1.12 release, the recommended way to build a client with this plugin, is to create a Spring Boot application, as explained on this page.

This page describes how to use the code generated by this plugin, in a Spring Boot application. This allows to use up-to-date Spring technology, like injection of dependencies, managing authentication with OAuth...

This page describes how to create such an app. But it's not a detailed Spring documentation. The necessary basis of Spring is explained. Once you're done with your first GraphQL Spring app, you'll find lots of documentation on the web to enhance it, according to your needs.

Notice: Spring container adds some latency when the app starts, as it needs to find and load all the app's components. But it's really worth the time: it allows to develop quicker applications that are much easier to maintain. And the magic of Spring Boot autoconfiguration is really impressive!

Tutorials

This page is an overview. It contains all the important information on how to create the app.

You'll find a detailed tutorial, with all steps on how to use the client, with two versions: a Gradle client tutorial and a a Maven client tutorial.

Summary

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)

What does the plugin generate?

When configuring the graphql-maven-plugin in client mode, it reads a GraphQL schema file, and generates all the necessary code to make it easy to call a GraphQL server.

As an overview, it generates:

  • One Executor class for each Query/Mutation/Subscription object. These Executors contain all the methods that allow to execute a full query, and shortcut methods to execute the queries, mutations and subscriptions.
    • The introspection queries (__schema and __type) are added to the query defined in the GraphQL schema. For "memory", you must provide a query in every GraphQL schema.
  • One POJO for each standard object of the GraphQL object
  • One GraphQLRequest object, that allows to store prepared queries
  • All the necessary runtime is actually attached as source code into your project: the generated code is stand-alone.
    • So, your project, when it runs, doesn't depend on any external dependency from graphql-java-generator.
      • This is why we call it an accelerator: you can generate the code once, and get rid of graphql-java-generator if you wish. BTW: we think its better to continue using it! This allows you to benefit from the enhancements the come next. But you're free to leave, and keep the generated code. :)
  • You can change this default behavior, and use the runtime into an external dependency

Creating a GraphQL Spring client app

Please note that all the code below is taken from the Forum sample, available in the graphql-maven-plugin-samples-Forum-client sample. This module is both a sample, and a project to execute integration tests of the plugin (there is a client and a server projects, to test both side of GraphQL).

You can access directly to this module with github in the plugin Maven git or in the plugin Gradle git.

Maven or Gradle configuration

First, you'll have to create or get your GraphQL schema. The GraphQL plugin expects a .graphqls file. See the GraphQL schema doc for all the details.

Then, add the plugin either to your POM file (if you're using Maven) or your build.gradle file (if you're using Gradle):

The POM file looks like this:

<project ...>
...
        <properties>
                <graphql-maven-plugin.version>1.12.3</graphql-maven-plugin.version>
        </properties>
...
        <build>
                <plugins>
...
                        <plugin>
                                <!-- Just to be sure that your code is based on java 8 or higher -->
                                <groupId>org.apache.maven.plugins</groupId>
                                <artifactId>maven-compiler-plugin</artifactId>
                                <configuration>
                                        <source>1.8</source>
                                        <target>1.8</target>
                                        <release>8</release>
                                </configuration>
                        </plugin>
                        <plugin>
                                <groupId>com.graphql-java-generator</groupId>
                                <artifactId>graphql-maven-plugin</artifactId>
                                <version>${graphql-maven-plugin.version}</version>
                                <executions>
                                        <execution>
                                                <goals>
                                                        <goal>generateClientCode</goal>
                                                </goals>
                                        </execution>
                                </executions>
                                <configuration>
                                        <packageName>my.target.package</packageName>
                                        <customScalars>
                                                <customScalar>
                                                        <graphQLTypeName>Date</graphQLTypeName>
                                                        <javaType>java.util.Date</javaType>
                                                        <graphQLScalarTypeStaticField>com.graphql_java_generator.customscalars.GraphQLScalarTypeDate.Date</graphQLScalarTypeStaticField>
                                                </customScalar>
                                        </customScalars>
                                        <!-- The parameters below change the 1.x default behavior. They are set to respect the default behavior of the future 2.x versions -->
                                        <generateDeprecatedRequestResponse>false</generateDeprecatedRequestResponse>
                                        <separateUtilityClasses>true</separateUtilityClasses>
                                        <!-- You can add here other plugin parameters -->
                                </configuration>
                        </plugin>
                        <plugin>
                                <!-- This helps by adding the generated source in the build path of your IDE -->
                                <groupId>org.codehaus.mojo</groupId>
                                <artifactId>build-helper-maven-plugin</artifactId>
                                <executions>
                                        <execution>
                                                <id>add-source</id>
                                                <phase>generate-sources</phase>
                                                <goals>
                                                        <goal>add-source</goal>
                                                </goals>
                                                <configuration>
                                                        <sources>
                                                                <source>Z:\target\checkout\target/generated-sources/graphql-maven-plugin</source>
                                                        </sources>
                                                </configuration>
                                        </execution>
                                </executions>
                        </plugin>
...
                        <extensions>
                                <!-- Adding these extensions prevents the error below, with JDK 9 and higher: -->
                                <!-- NoSuchMethodError: 'java.lang.String javax.annotation.Resource.lookup()' -->
                                <extension>
                                        <groupId>javax.annotation</groupId>
                                        <artifactId>javax.annotation-api</artifactId>
                                        <version>1.3.2</version>
                                </extension>
                                <extension>
                                        <groupId>javax.annotation</groupId>
                                        <artifactId>jsr250-api</artifactId>
                                        <version>1.0</version>
                                </extension>
                        </extensions>
        
                </plugins>
        </build>
...
        <dependencies>
                <!-- Dependencies for GraphQL -->
                <dependency>
                        <groupId>com.graphql-java-generator</groupId>
                        <artifactId>graphql-java-runtime</artifactId>
                        <version>${graphql-maven-plugin.version}</version>
                </dependency>
...
        </dependencies>
...
</project>

The build.gradle file looks like this:

plugins {
        id "com.graphql_java_generator.graphql-gradle-plugin" version "1.12.3"
        id 'java'
}

repositories {
        jcenter()
        mavenCentral()
}

dependencies {
        // THE VERSION MUST BE THE SAME AS THE PLUGIN's ONE
        implementation "com.graphql-java-generator:graphql-java-runtime:1.12.3"
}

// The line below makes the GraphQL plugin be executed before Java compiles, so that all sources are generated on time
compileJava.dependsOn generateClientCode

// The line below adds the generated sources as a java source folder
sourceSets.main.java.srcDirs += '/build/generated/graphql-maven-plugin'

// Let's configure the GraphQL Gradle Plugin:
// All available parameters are described here: 
// https://graphql-maven-plugin-project.graphql-java-generator.com/graphql-maven-plugin/generateClientCode-mojo.html
generateClientCodeConf {
        packageName = 'org.forum.client'
        customScalars = [ [
                        graphQLTypeName: "Date",
                        javaType: "java.util.Date",
                        graphQLScalarTypeStaticField: "com.graphql_java_generator.customscalars.GraphQLScalarTypeDate.Date"
        ] ]

        // The parameters below change the 1.x default behavior. They are set to respect the default behavior of the future 2.x versions 
        generateDeprecatedRequestResponse = false
        separateUtilityClasses = true
        
        // You can add here other plugin parameters
}

You can define the package that will contain the generated code. If you don't, the default package is com.generated.graphql.

The necessary runtime source code is joined into the generated code, and remains in its original package, which is com.graphql_java_generator.*. So everything is embedded. Read the Howto personalize the generated code if you want to change this default behavior.

First build

The first build is important, before starting coding. It generates all the POJO, based on the GraphQL schema, and all the utility files.

For Maven, do:

mvn clean install

For Gradle, do:

gradle clean build

Once you've done that, the generated code is available in target/generated-sources/graphql-maven-plugin. Thanks to the build-helper-maven-plugin, this folder should be available as a source folder in your IDE.

The Spring conf

The following properties file should be named application.properties (it can also be yaml file, named application.yml), and be stored in the root of the resource folder.

This file is the standard Spring Boot configuration file. So you'll find doc about it on the web. And you can add other configuration stuff for Spring Boot here.

graphql.endpoint.url = http://localhost:8180/graphql
#You may specify a different URL for subscription. If not defined, subscription are executed against the above url (standard case)
graphql.endpoint.subscriptionUrl = http://localhost:8180/graphql/subscription

#We don't need the Netty web server to start
spring.main.web-application-type = none

The minimal Spring app

We can now create a first app. As we're in a Spring mode, we're using Spring Boot.

package com.graphql_java_generator.minimal_app;

import java.util.Date;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import com.graphql_java_generator.client.GraphQLConfiguration;
import com.graphql_java_generator.samples.forum.client.graphql.forum.client.MutationTypeExecutor;
import com.graphql_java_generator.samples.forum.client.graphql.forum.client.QueryTypeExecutor;
import com.graphql_java_generator.samples.forum.client.graphql.forum.client.SubscriptionTypeExecutor;

@SpringBootApplication(scanBasePackageClasses = { MinimalSpringApp.class, GraphQLConfiguration.class, QueryTypeExecutor.class })
public class MinimalSpringApp implements CommandLineRunner {

        /**
         * The executor, that allows to execute GraphQL queries. The class name is the one defined in the GraphQL schema.
         */
        @Autowired
        QueryTypeExecutor queryExecutor;

        /**
         * The executor, that allows to execute GraphQL mutations. The class name is the one defined in the GraphQL schema.
         * It will be null if no mutation has been defined.
         */
        @Autowired(required = false)
        MutationTypeExecutor mutationExecutor;

        /**
         * The executor, that allows to execute GraphQL subscriptions. The class name is the one defined in the GraphQL
         * schema. It will be null if no subscription has been defined.
         */
        @Autowired(required = false)
        SubscriptionTypeExecutor subscriptionExecutor;

        public static void main(String[] args) {
                SpringApplication.run(MinimalSpringApp.class, args);
        }

        /**
         * This method is started by Spring, once the Spring context has been loaded. This is run, as this class implements
         * {@link CommandLineRunner}
         */
        @Override
        public void run(String... args) throws Exception {
                // A basic demo of input parameters
                Date date = new Date(2019 - 1900, 12 - 1, 20);

                // For this simple sample, we execute a direct query. But prepared queries are recommended.
                // Please note that input parameters are mandatory for list or input types.
                System.out.println("Executing query: '{id name publiclyAvailable topics(since: &param){id}}', with input parameter param of value '" + date + "'");
                System.out.println(queryExecutor.boards("{id name publiclyAvailable topics(since: &param){id}}", "param", date));
        }
}

Here are the main explanation about this class:

  • The @SpringBootApplication annotation indicates to Spring that it's a Spring Boot app.
    • Its scanBasePackageClasses parameter contains three classes, that indicates the root packages, from which Spring will look for Spring beans.
    • You must give at least your main class, and the GraphQLConfiguration and QueryTypeExecutor classes.
  • The @Autowired annotation indicates to Spring that it must initialize this attributes with the relevant Spring bean, found from the root packages indicated by scanBasePackageClasses.
    • These attributes will be initialized, before the run method is called.
  • The CommandLineRunner indicates to Spring that it must start the class as a command line executor, that is: all the arguments received by the main method are relayed to this command line executor. The app won't stop until all the found CommandLineRunners are done, that is that the execution of their run is finished.
    • You may provide several CommandLineRunners. In this case, they will run in parallel. See the Spring doc for more information.
  • The run method:
    • Initializes a date variable, to show the use of input parameters.
    • Uses the queryExecutor to execute a boards query. The name of the query is the one defined in the GraphQL schema.
    • This sample provides the input parameter value (or values, as query/mutation/subscription may contain more than one input parameters), from this couple if method parameters:
      • The name of the parameter, as defined in the query string. Here: param
      • The runtime value of the parameter.

Creating a Spring 'GraphQL requests' bean

The idea

A good idea, when using Spring, is to group the GraphQL requests in one or more specific Spring beans.

You can find a sample of that in the Forum client sample.

An implementation for this idea would be to :

  • Create an interface, for the bean
  • Implement it, and mark it with the @Component Spring annotation
  • Inject it in the class(es) that need to execute GraphQL query, mutation and/or subscription

You'll find below hints on how to do that.

The interface

It allows to define and group all your GraphQL queries, mutations and/or subscriptions. If it's too big, it may be interesting to have several such interfaces.

It's a standard interface. Here is a sample, based on the Forum client sample:

public interface GraphQLRequests {

        static final String DATE_FORMAT = "yyyy-MM-dd";
        static final SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);

        List<Board> boardsSimple() throws GraphQLRequestPreparationException, GraphQLRequestExecutionException;

        List<Board> boardsAndTopicsWithFieldParameter(Date since)
                        throws GraphQLRequestPreparationException, GraphQLRequestExecutionException;

        List<Topic> topicAuthorPostAuthor(String boardName, Date since)
                        throws GraphQLRequestPreparationException, GraphQLRequestExecutionException;
}    

The Spring bean

Then you implement this class. At this point, you must be cautious about two points:

  • Don't forget the @Component Spring annotation
  • The class must in the package of one of the class marked in the scanBasePackageClasses parameter of the @SpringBootApplication annotation, or in one of their sub-packages.

Here is a sample:

@Component
public class GraphQLRequestsImpl implements GraphQLRequests {

        @Autowired
        QueryTypeExecutor queryTypeExecutor;
        @Autowired
        MutationTypeExecutor mutationTypeExecutor;

        // Below are the GraphQLRequests, that are created at initialization time.
        GraphQLRequest boardsSimpleRequest;
        GraphQLRequest boardsAndTopicsRequest;
        GraphQLRequest topicAuthorPostAuthorRequest;

        @PostConstruct
        public void init() throws GraphQLRequestPreparationException {
                // No field specified: all scalar fields of the root type will be queried
                boardsSimpleRequest = queryTypeExecutor.getBoardsGraphQLRequest(null);
                boardsAndTopicsRequest = queryTypeExecutor.getBoardsGraphQLRequest("{id name publiclyAvailable topics(since:?since){id}}");
                topicAuthorPostAuthorRequest = queryTypeExecutor
                                .getTopicsGraphQLRequest("{id date author{name email alias id type} nbPosts title content "
                                                + "posts(memberId:?memberId, memberName: ?memberName, since: &sinceParam){id date author{name email alias} title content}}");
        }

        @Override
        public List<Board> boardsSimple() throws GraphQLRequestExecutionException {
                return queryTypeExecutor.boards(boardsSimpleRequest);
        }

        @Override
        public List<Board> boardsAndTopicsWithFieldParameter(Date since)
                        throws GraphQLRequestExecutionException, GraphQLRequestPreparationException {
                return queryTypeExecutor.boards(boardsAndTopicsRequest, "since", since);
        }

        @Override
        public List<Topic> topicAuthorPostAuthor(String boardName, Date since) throws GraphQLRequestExecutionException {
                return queryTypeExecutor.topics(topicAuthorPostAuthorRequest, boardName, "sinceParam", since);
        }
}

The use of this bean

The beans created here above can be used in any other Spring Bean, through bean injection. You'll find a sample below.

The important points here, are:

  • This class must be a component Bean. It must not be created by the new operator, like new MyComponentThatExecutesGraphQLQueries(). See the Spring doc for more information.
  • Don't forget to mark the class with the @Component Spring annotation
  • Don't forget the @Autowired Spring annotation, so that the GraphQLRequests attribute is properly filled by Spring.
@Component
public class MyComponentThatExecutesGraphQLQueries {

        @Autowired
        GraphQLRequests requests;

        public void exec() throws GraphQLRequestPreparationException, GraphQLRequestExecutionException {
                System.out.println("----------------------------------------------------------------------------");
                System.out.println("----------------  boardsSimple  --------------------------------------------");
                System.out.println(requests.boardsSimple());

                System.out.println("----------------------------------------------------------------------------");
                System.out.println("----------------  topicAuthorPostAuthor  -----------------------------------");
                Calendar cal = Calendar.getInstance();
                cal.set(2018, 12, 20);
                System.out.println(requests.topicAuthorPostAuthor("Board name 2", cal.getTime()));

                System.out.println("----------------------------------------------------------------------------");
                System.out.println("----------------  createBoard  ---------------------------------------------");
                // We need a unique name. Let's use a random name for that, if none was provided.
                name = (name != null) ? name : "Name " + Float.floatToIntBits((float) Math.random() * Integer.MAX_VALUE);
                System.out.println(requests.createBoard(name, true));
        }
}

How does it work? (GraphQL autoconfiguration, and specific Spring beans)

As explained here above, the Main class is marked by the @SpringBootApplication annotation.

Then, the plugin's runtime contains this class: com.graphql_java_generator.spring.client.GraphQLAutoConfiguration. It is defined in the META-INF/spring.factories resource file.

This class contains the default configuration, to make the app works. It's actually a set of Spring Beans, that will be loaded, of no equivalent bean has been defined by the application.

For instance, this class defines:

  • The graphqlEndpoint String bean. It contains the URL that has been defined in the application.properties or application.yml file.
  • The graphqlSubscriptionEndpoint String is defined the same way. It's null, of course, of not defined in the application configuration file.
  • The queryExecutor Bean is of type com.graphql_java_generator.client.QueryExecutor. He is responsible for the execution of the GraphQL requests (queries, mutations and subscriptions).
    • The queryExecutor defined in the GraphQLAutoConfiguration is the com.graphql_java_generator.client.QueryExecutorSpringReactiveImpl that has been created in the 1.12 release.
    • If you want to get back to the "old" one, you can define a queryExecutor bean, for instance in your Main class, that will return an instance of the com.graphql_java_generator.client.QueryExecutorImpl.
  • A webClient bean, of type org.springframework.web.reactive.function.client.WebClient, that executes the queries and mutations.
  • A webSocketClient bean, of type org.springframework.web.reactive.socket.client.WebSocketClient, that executes the subscriptions.

You can override all these beans, by just defining a bean of the same name and type, for instance in your Main class, that will return an instance of the com.graphql_java_generator.client.QueryExecutorImpl.