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!
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.
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:
It manages two ways of executing the request:
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)
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:
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.
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.
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 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
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: ¶m){id}}', with input parameter param of value '" + date + "'"); System.out.println(queryExecutor.boards("{id name publiclyAvailable topics(since: ¶m){id}}", "param", date)); } }
Here are the main explanation about this class:
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 :
You'll find below hints on how to do that.
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; }
Then you implement this class. At this point, you must be cautious about two points:
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 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:
@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)); } }
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:
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.