This is a maven 3 plugin for generating boilerplate Java classes from GraphQL types and operations.
- Uses only GraphQL files as input
- Runs with or without POM configuration
- Can be used on both client and server side
- Generates only classes necessary to represent types and operations
- Generated code has no dependencies by default
This goal runs the classes generation and is bound to the phase generate-sources by default.
Usually, a server app needs all GraphQL schema types to be represented by Java classes. The plugin can be configured
to output Java classes for GraphQL schema types by setting generatedOutputTypes
property value to SCHEMA_TYPES
.
Example
The GraphQL types
type TreeNode {
left: TreeNode
right: TreeNode
value: String
}
interface Interface1 {
switch: TreeNode
}
could be translated into the following Java classes
package pkg;
/* imports */
public interface Interface1
{
TreeNode getSwitch();
}
package pkg;
/* imports */
public class TreeNode
{
private TreeNode _left_ = null;
public TreeNode getLeft() {
return this._left_;
}
public void setLeft(TreeNode v) {
java.util.stream.Stream.of(v)
.forEach($ -> {});
this._left_ = v;
}
private String _value_ = null;
public String getValue() {
return this._value_;
}
public void setValue(String v) {
java.util.stream.Stream.of(v)
.forEach($ -> {});
this._value_ = v;
}
private TreeNode _right_ = null;
public TreeNode getRight() {
return this._right_;
}
public void setRight(TreeNode v) {
java.util.stream.Stream.of(v)
.forEach($ -> {});
this._right_ = v;
}
/* equals, hashCode, toString */
}
A client app can define its own operations. Usually, there could be a file with definitions of operations and
fragments. The generatedOutputTypes
property value should be set to DEFINED_OPERATIONS
to enable generation of Java
classes for GraphQL operations defined in files. The following classes will be generated for each operation:
- a wrapper class, which requires variables to be set and provides the operation name, document, variables and the root result class
- a variables class, which is used by the wrapper class to collect variables
- a set of result classes, which contain the fields requested in the operation
The generated result classes will contain only the requested fields. Union (or interface) result classes will contain all the requested fields of all the requested union members (or interface implementors). Aliases must be set to avoid name collisions for fields from different fragments.
Result example
Input file with operations
fragment ValueFragment on Type1MutationField2 {
t1Value: value
}
fragment ValueFragment on Type2MutationField2 {
t2Value: value {
...ValueFragment
}
}
fragment ValueFragment on Type3MutationField2 {
t3Value: value
}
mutation updateField2($id: [ID!]! = ["0"]) {
field2(arg1: $id) {
id name
...on Type2MutationField2 {
...ValueFragment
}
... on Type1MutationField2 {
...ValueFragment
}
}
}
could be translated into the following classes
package pkg;
/* imports */
public class UpdateField2MutationResult
{
private java.util.List<pkg.field2.Interface1MutationField2Result> _field2_ = null;
public java.util.List<pkg.field2.Interface1MutationField2Result> getField2() {
return this._field2_;
}
public void setField2(java.util.List<pkg.field2.Interface1MutationField2Result> v) {
java.util.stream.Stream.of(v)
.filter(java.util.Objects::nonNull)
.flatMap(java.util.Collection::stream)
.forEach($ -> {});
this._field2_ = v;
}
/* equals, hashCode, toString */
}
package pkg;
/* imports */
public class Interface1MutationField2Result
{
private String _name_ = null;
public String getName() {
return this._name_;
}
public void setName(String v) {
java.util.stream.Stream.of(v)
.map(java.util.Objects::requireNonNull)
.forEach($ -> {});
this._name_ = v;
}
private pkg.field2.t2Value.Type3MutationField2Result _t2Value_ = null;
public pkg.field2.t2Value.Type3MutationField2Result getT2Value() {
return this._t2Value_;
}
public void setT2Value(pkg.field2.t2Value.Type3MutationField2Result v) {
java.util.stream.Stream.of(v)
.forEach($ -> {});
this._t2Value_ = v;
}
private Integer _t1Value_ = null;
public Integer getT1Value() {
return this._t1Value_;
}
public void setT1Value(Integer v) {
java.util.stream.Stream.of(v)
.forEach($ -> {});
this._t1Value_ = v;
}
private String _id_ = null;
public String getId() {
return this._id_;
}
public void setId(String v) {
java.util.stream.Stream.of(v)
.map(java.util.Objects::requireNonNull)
.forEach($ -> {});
this._id_ = v;
}
/* equals, hashCode, toString */
}
package pkg;
/* imports */
public class Type3MutationField2Result
{
private Integer _t3Value_ = null;
public Integer getT3Value() {
return this._t3Value_;
}
public void setT3Value(Integer v) {
java.util.stream.Stream.of(v)
.forEach($ -> {});
this._t3Value_ = v;
}
/* equals, hashCode, toString */
}
A client app may need to create GraphQL operations at run time without defining them statically in files.
This can be achieved by setting generatedOutputTypes
property to DYNAMIC_OPERATIONS
. The following Java classes will
be generated for each schema operation:
- a wrapper class, which requires a selection set and provides the operation document and the root result class
Additionally, the following classes, shared by all dynamic operations, will be generated:
- selector classes, which are used by wrapper classes to collect field selection set
- result classes, which contain the response fields requested in any selection set
The generated result classes will contain all possible fields. This includes union (or interface) results which are represented by classes containing all the fields of all the union members (or interface implementors) using automatically generated aliases to avoid field name collisions.
Usage example
The following code in Java
public class Class {
public Query getQuery() {
return new DynamicQuery(query -> query
.getField2(
field2Arguments -> field2Arguments
.setArg2(List.of(new InputQueryArg2()))
.setAfter("123"),
unionQueryField2Connection -> unionQueryField2Connection
.getPageInfo(pageInfo -> pageInfo
.getStartCursor()
.getEndCursor()
.getHasNextPage()
.getHasPreviousPage())
.getEdges(unionQueryField2ConnectionEdge -> unionQueryField2ConnectionEdge
.getCursor()
.getNode(unionQueryField2 -> unionQueryField2
.onType1UnionField2(type1UnionField2 -> type1UnionField2
.getFieldA()
.getSwitch(TreeNodeResultSelector::getValue))
.onType2UnionField2(type2UnionField2 -> type2UnionField2
.getName()
.getInt())
.onType3UnionField2(type3UnionField2 -> type3UnionField2
.getName()
.getFieldC())))));
}
}
could produce a GraphQL operation like the below
query {
field2(
arg2: [{ id: null }]
after: """123"""
) {
pageInfo {
startCursor
endCursor
hasNextPage
hasPreviousPage
}
edges {
cursor
node {
... on Type1UnionField2 {
fieldA_Type1UnionField2: fieldA
switch_Type1UnionField2: switch {
value
}
}
... on Type2UnionField2 {
name_Type2UnionField2: name
int_Type2UnionField2: int
}
... on Type3UnionField2 {
name_Type3UnionField2: name
fieldC_Type3UnionField2: fieldC
}
}
}
}
}
All generated Java classes representing GraphQL types will have public getters for all fields. The private fields can be
renamed to avoid Java keyword collisions by setting privateFieldPrefix
and/or privateFieldSuffix
.
If a serialization engine accesses fields directly and not methods, it is possible to set jsonPropertyAnnotation
property (i.e.com.google.gson.annotations.SerializedName
) to allow access by exact GraphQL field names.
GraphQL | Java |
---|---|
scalar |
String , matching Java class or mapped Java class |
enum |
enum |
interface |
interface with accessors for each GraphQL field |
union |
empty interface which is added to the list of implemented interfaces of Java classes representing union members |
type or input |
class with private fields with public accessors |
query , mutation or subscription |
class accepting variables, arguments, selection sets and providing operation name, operation document, variables, result class |
[] |
java.util.List |
! |
null check in setters |
POM Property | Type | Description |
---|---|---|
source | FileSet |
Set of source files including both schema files and operation files. |
sources | FileSet[] |
Set of source files including both schema files and operation files. |
outputDirectory | File |
The root directory for generated files. Default: "${project.build.directory}/generated-sources/graphql-classes/java" |
packageName | String |
Name of the base package for generated classes. Default: "gql.generated" |
scalarMap | Map<String,String> |
Mapping of GraphQL scalars to Java classes. Default: Int -> Integer Float -> Double ID -> String |
aliasMap | Map<String,String> |
Mapping of GraphQL field names to GraphQL field aliases. Can be used to avoid Java keyword collisions for dynamic operations only. |
importPackages | Set<String> |
Set of packages to import into generated classes. |
jsonPropertyAnnotation | String |
Annotation to be used on generated private fields. |
privateFieldPrefix | String |
Prefix to be added to generated private field names to avoid Java keywords collisions. |
privateFieldSuffix | String |
Suffix to be added to generated private field names to avoid Java keywords collisions. Default: "__" , when jsonPropertyAnnotation is set |
generatedAnnotationVersion | String |
The version of @Generated annotation to use on generated classes (i.e. "1.8", "11", "15", ...). At the moment, the real difference is between "1.8" and the rest. |
dataObjectEnhancement | DataObjectEnhancementType |
The type of data object enhancement. Can be empty or take one of the following values: METHOD_CHAINING (data object setters will return 'this' instead of 'void'), BUILDER (data objects will use builder pattern), VALUE (data objects will use value pattern). |
generatedOutputTypes | Set<GeneratedOutputType> |
The scope of the plugin output. Can be empty or take one or many values from the following list: SCHEMA_TYPES (all the types defined in GraphQL schema files), DEFINED_OPERATIONS (all the operations defined in input files), DYNAMIC_OPERATIONS (one operation per schema entry allowing to construct operations at runtime). Default: DEFINED_OPERATIONS |
parserMaxTokens | Integer |
Maximum number of tokens to process by the GraphQL engine. |
Example
<plugin>
<groupId>com.github.alex079</groupId>
<artifactId>graphql-classes-maven-plugin</artifactId>
<version>${VERSION}</version>
<configuration>
<source>
<includes>
<include>schema.graphqls</include>
<include>*.graphql</include>
</includes>
</source>
<packageName>integration.test</packageName>
<outputDirectory>${project.build.directory}\generated-gql-sources\java</outputDirectory>
<scalarMap>
<Address>String</Address>
</scalarMap>
<importPackages>
<value>java.math</value>
</importPackages>
<jsonPropertyAnnotation>com.google.gson.annotations.SerializedName</jsonPropertyAnnotation>
<dataObjectEnhancement>METHOD_CHAINING</dataObjectEnhancement>
<generatedAnnotationVersion>1.8</generatedAnnotationVersion>
</configuration>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
Command Line Property | Type | Description |
---|---|---|
gql.sourceDirectory | File |
Directory containing source files. Default: current directory |
gql.sourceIncludes | Set<String> |
Set of patterns to include. |
gql.sourceExcludes | Set<String> |
Set of patterns to exclude. |
gql.outputDirectory | File |
The root directory for generated files. Default: "./generated-sources/graphql-classes/java" |
gql.packageName | String |
Name of the base package for generated classes. Default: "gql.generated" |
gql.scalarMap | Set<String> |
Mapping of GraphQL scalars to Java classes formatted as a list of key=value pairs. Default: Int=Integer,Float=Double,ID=String |
gql.aliasMap | Set<String> |
Mapping of GraphQL field names to GraphQL field aliases formatted as a list of key=value pairs. Can be used to avoid Java keyword collisions for dynamic operations only. |
gql.importPackages | Set<String> |
Set of packages to import into generated classes. |
gql.jsonPropertyAnnotation | String |
Annotation to be used on generated private fields. |
gql.privateFieldPrefix | String |
Prefix to be added to generated private field names to avoid Java keywords collisions. |
gql.privateFieldSuffix | String |
Suffix to be added to generated private field names to avoid Java keywords collisions. Default: "__" , when jsonPropertyAnnotation is set |
gql.generatedAnnotationVersion | String |
The version of @Generated annotation to use on generated classes (i.e. "1.8", "11", "15", ...). At the moment, the real difference is between "1.8" and the rest. |
gql.dataObjectEnhancement | DataObjectEnhancementType |
The type of data object enhancement. Can be empty or take one of the following values: METHOD_CHAINING (data object setters will return 'this' instead of 'void'), BUILDER (data objects will use builder pattern), VALUE (data objects will use value pattern). |
gql.generatedOutputTypes | Set<GeneratedOutputType> |
The scope of the plugin output. Can be empty or take one or many values from the following list: SCHEMA_TYPES (all the types defined in GraphQL schema files), DEFINED_OPERATIONS (all the operations defined in input files), DYNAMIC_OPERATIONS (one operation per schema entry allowing to construct operations at runtime). Default: DEFINED_OPERATIONS |
gql.parserMaxTokens | Integer |
Maximum number of tokens to process by the GraphQL engine. |
Example
mvn com.github.alex079:graphql-classes-maven-plugin:${VERSION}:generate \
-Dgql.sourceDirectory=src/main/resources \
-Dgql.sourceIncludes=*.graphql,*.graphqls \
-Dgql.outputDirectory=target/generated-sources/java \
-Dgql.importPackages=java.time \
-Dgql.scalarMap=CustomType1=String,CustomType2=Integer \
-Dgql.generatedAnnotationVersion=1.8