1. Overview
GraphQL is a relatively new concept for building web services as an alternative to REST. A few Java libraries have recently emerged for creating and calling GraphQL services.
In this tutorial, we’ll look at GraphQL schema, queries, and mutations. We’ll see how to create and mock a simple GraphQL server in plain Java. Then, we’ll explore how to make calls to GraphQL services using well-known HTTP libraries.
Finally, we’ll also explore the available 3rd party libraries for making GraphQL service calls.
2. GraphQL
GraphQL is a query language for web services and a server-side runtime for executing queries using a type system.
A GraphQL server specifies the API capabilities using a GraphQL schema. This allows the GraphQL client to specify exactly which data to retrieve from the API. This may include child resources and multiple queries in a single request.
2.1. GraphQL Schema
A GraphQL server defines services with a set of types. Those types describe the set of possible data you can query using the service.
GraphQL services can be written in any language. However, GraphQL schemas need to be defined using a DSL called GraphQL schema language.
In our example GraphQL schema, we’ll define two types (Book and Author) and a single query operation to fetch all books (allBooks):
type Book {
title: String!
author: Author
}
type Author {
name: String!
surname: String!
}
type Query {
allBooks: [Book]
}
schema {
query: Query
}
The Query type is special because it defines the entry point of a GraphQL query.
2.2. Queries and Mutations
A GraphQL service is created by defining types and fields, as well as providing functions for different fields.
In its simplest form, GraphQL is about asking for specific fields on objects. For example, we might query to fetch all book titles:
{
"allBooks" {
"title"
}
}
Even though it looks similar, this is not JSON. It’s a special GraphQL query format that supports arguments, aliases, variables, and more.
A GraphQL service would respond to the above query with a JSON formatted response like this:
{
"data": {
"allBooks": [
{
"title": "Title 1"
},
{
"title": "Title 2"
}
]
}
}
In this tutorial, we’ll focus on data fetching using queries. However, it’s important to mention another special concept within GraphQL – mutation.
Any operation that can cause modification is sent using a mutation type.
3. GraphQL Server
Let’s create a simple GraphQL server in Java using the schema we defined above. We’ll make use of the GraphQL Java library for our GraphQL server implementation.
We’ll start by defining our GraphQL query and implement the allBooks method specified in our example GraphQL schema:
public class GraphQLQuery implements GraphQLQueryResolver {
private BookRepository repository;
public GraphQLQuery(BookRepository repository) {
this.repository = repository;
}
public List<Book> allBooks() {
return repository.getAllBooks();
}
}
Next, in order to expose our GraphQL endpoint, we’ll create a web servlet:
@WebServlet(urlPatterns = "/graphql")
public class GraphQLEndpoint extends HttpServlet {
private SimpleGraphQLHttpServlet graphQLServlet;
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
graphQLServlet.service(req, resp);
}
@Override
public void init() {
GraphQLSchema schema = SchemaParser.newParser()
.resolvers(new GraphQLQuery(new BookRepository()))
.file("schema.graphqls")
.build()
.makeExecutableSchema();
graphQLServlet = SimpleGraphQLHttpServlet
.newBuilder(schema)
.build();
}
}
In the servlet init method, we’ll parse our GraphQL schema located in the resources folder. Finally, using the parsed schema, we can create an instance of a SimpleGraphQLHttpServlet.
We’ll make use of the maven-war-plugin to package our application and jetty-maven-plugin to run it:
mvn jetty:run
Now we are ready to run and test our GraphQL service by sending a request to:
http://localhost:8080/graphql?query={allBooks{title}}
4. HTTP Client
As with REST services, GraphQL services are exposed via the HTTP protocol. Therefore, we can use any Java HTTP client to make a call to a GraphQL service.
4.1. Sending Requests
Let’s try to send a request to a GraphQL service we created in the previous section:
public static HttpResponse callGraphQLService(String url, String query)
throws URISyntaxException, IOException {
HttpClient client = HttpClientBuilder.create().build();
HttpGet request = new HttpGet(url);
URI uri = new URIBuilder(request.getURI())
.addParameter("query", query)
.build();
request.setURI(uri);
return client.execute(request);
}
In our example, we used Apache HttpClient. However, any Java HTTP client can be used.
4.2. Parsing Responses
Next, let’s parse the response from a GraphQL service. GraphQL services send JSON formatted responses, same as REST services:
HttpResponse httpResponse = callGraphQLService(serviceUrl, "{allBooks{title}}");
String actualResponse = IOUtils.toString(httpResponse.getEntity().getContent(), StandardCharsets.UTF_8.name());
Response parsedResponse = objectMapper.readValue(actualResponse, Response.class);
assertThat(parsedResponse.getData().getAllBooks()).hasSize(2);
In our example, we used ObjectMapper from the popular Jackson library. However, we may use any Java library for JSON serialization/deserialization.
4.3. Mocking Responses
As with any other service exposed via HTTP, we can mock GraphQL server responses for testing purposes.
We can make use of the MockServer library for stubbing external GraphQL HTTP services:
String requestQuery = "{allBooks{title}}";
String responseJson = "{\"data\":{\"allBooks\":[{\"title\":\"Title 1\"},{\"title\":\"Title 2\"}]}}";
new MockServerClient(SERVER_ADDRESS, serverPort)
.when(
request()
.withPath(PATH)
.withQueryStringParameter("query", requestQuery),
exactly(1)
)
.respond(
response()
.withStatusCode(HttpStatusCode.OK_200.code())
.withBody(responseJson)
);
Our example mock server will accept a GraphQL query as a parameter and respond with a JSON response in the body.
5. External Libraries
A couple of Java GraphQL libraries that allow even simpler GraphQL service calls have emerged recently.
5.1. American Express Nodes
Nodes is a GraphQL client from American Express designed for constructing queries from standard model definitions. To start using it, we should first add the required dependency:
<dependency>
<groupId>com.github.americanexpress.nodes</groupId>
<artifactId>nodes</artifactId>
<version>0.5.0</version>>
</dependency>
The library is currently hosted on JitPack, which we should also add to our Maven installation repositories:
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
Once the dependency is resolved, we can make use of the GraphQLTemplate to construct a query and call our GraphQL service:
public static GraphQLResponseEntity<Data> callGraphQLService(String url, String query)
throws IOException {
GraphQLTemplate graphQLTemplate = new GraphQLTemplate();
GraphQLRequestEntity requestEntity = GraphQLRequestEntity.Builder()
.url(StringUtils.join(url, "?query=", query))
.request(Data.class)
.build();
return graphQLTemplate.query(requestEntity, Data.class);
}
Nodes will parse a response from the GraphQL service using the class we specified:
GraphQLResponseEntity<Data> responseEntity = callGraphQLService(serviceUrl, "{allBooks{title}}");
assertThat(responseEntity.getResponse().getAllBooks()).hasSize(2);
We should note that Nodes still requires us to construct our own DTO classes for parsing the response.
5.2. GraphQL Java Generator
The GraphQL Java Generator library makes use of the ability to generate Java code based on GraphQL schema.
This approach is similar to WSDL code generators used in SOAP services. To start using it, we should first add the required dependency:
<dependency>
<groupId>com.graphql-java-generator</groupId>
<artifactId>graphql-java-runtime</artifactId>
<version>1.18</version>
</dependency>
Next, we can configure graphql-maven-plugin to execute a generateClientCode goal:
<plugin>
<groupId>com.graphql-java-generator</groupId>
<artifactId>graphql-maven-plugin</artifactId>
<version>1.18</version>
<executions>
<execution>
<goals>
<goal>generateClientCode</goal>
</goals>
</execution>
</executions>
<configuration>
<packageName>com.baeldung.graphql.generated</packageName>
<copyRuntimeSources>false</copyRuntimeSources>
<generateDeprecatedRequestResponse>false</generateDeprecatedRequestResponse>
<separateUtilityClasses>true</separateUtilityClasses>
</configuration>
</plugin>
Once we run the Maven build command, the plugin will generate both DTOs and utility classes required for calling our GraphQL service.
The generated QueryExecutor component will contain methods for calling our GraphQL service and parsing its response:
public List<Book> allBooks(String queryResponseDef, Object... paramsAndValues)
throws GraphQLRequestExecutionException, GraphQLRequestPreparationException {
logger.debug("Executing query 'allBooks': {} ", queryResponseDef);
ObjectResponse objectResponse = getAllBooksResponseBuilder()
.withQueryResponseDef(queryResponseDef).build();
return allBooksWithBindValues(objectResponse,
graphqlClientUtils.generatesBindVariableValuesMap(paramsAndValues));
}
However, it’s built for use with the Spring framework.
6. Conclusion
In this article, we explored how to make a call to a GraphQL service from a Java application.
We learned how to create and mock a simple GraphQL server. Next, we saw how to send a request and retrieve a response from a GraphQL server using a standard HTTP library. We also saw how to parse the GraphQL service responses from JSON to a Java object.
Finally, we looked at two available 3rd party libraries for making GraphQL service calls, Nodes and GraphQL Java Generator.
As always, the complete source code is available over on GitHub.