Return Anydata From GraphQL Mutation
In GraphQL, a mutation is used to modify server-side data, such as creating, updating or deleting resources. In many cases, you may want to return arbitrary or flexible data structures in response, which could include custom objects, nested fields, status messages or error details. Designing mutations to return such diverse outputs enhances API usability, especially when communicating both success and failure scenarios to clients in a structured way.
This article explores how to implement flexible return types in GraphQL mutations by defining and using union types for dynamic response handling.
1. Using Union Types for Mutation Responses
In GraphQL, mutations often need to return different types of responses depending on whether the operation was successful or not. Rather than using a generic response object with optional fields, GraphQL provides union types as a clean and type-safe way to handle this variability.
A union type allows a field to return one of several distinct object types. This is useful when the possible response types do not share the same fields. In the example from this article, the removeCustomer mutation can return either a RemoveCustomerSuccess object if the operation is successful, or an ErrorResponse object if the customer ID is not found..
union RemoveCustomerResult = RemoveCustomerSuccess | ErrorResponse
This union type allows the mutation to return one of the two types, preserving flexibility and type safety.
2. GraphQL Schema Definition – schema.graphqls
We start by defining the GraphQL schema. This schema introduces a Mutation for removing a customer and uses a union type called RemoveCustomerResult to represent multiple return possibilities.
Create the file src/main/resources/graphql/schema.graphqls:
type Query {
allCustomers: [Customer!]!
}
type Mutation {
removeCustomer(id: ID!): RemoveCustomerResult!
}
type Customer {
id: ID!
name: String!
}
type RemoveCustomerSuccess {
message: String!
deletedCustomerId: ID!
}
type ErrorResponse {
errorCode: String!
message: String!
}
union RemoveCustomerResult = RemoveCustomerSuccess | ErrorResponse
The removeCustomer mutation accepts a customer ID and returns a RemoveCustomerResult, which is a union of RemoveCustomerSuccess and ErrorResponse. This means the mutation can return either a success or an error depending on the outcome of the operation.
3. DTO and Model Classes
To support the union types in the schema, we need to define the corresponding Java classes that represent each possible return type from the mutation. Each class should match the structure defined in the GraphQL schema.
The class below represents the successful outcome of a customer removal operation.
public class RemoveCustomerSuccess {
private String message;
private int deletedCustomerId;
public RemoveCustomerSuccess() {
}
public RemoveCustomerSuccess(String message, int deletedCustomerId) {
this.message = message;
this.deletedCustomerId = deletedCustomerId;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public int getDeletedCustomerId() {
return deletedCustomerId;
}
public void setDeletedCustomerId(int deletedCustomerId) {
this.deletedCustomerId = deletedCustomerId;
}
}
This POJO is mapped to the RemoveCustomerSuccess GraphQL type. It carries a message and the ID of the deleted customer.
In case the mutation fails, an error object is returned using this class.
public class ErrorResponse {
private String errorCode;
private String message;
// Constructors, getters, setters
}
Next, we define a simple POJO to represent the Customer entity.
public class Customer {
private int id;
private String name;
public Customer() {
}
public Customer(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
4. GraphQL Configuration
To make GraphQL aware of how to resolve the union type RemoveCustomerResult, we configure a custom type resolver.
@Configuration
public class GraphQLConfig {
@Bean
public RuntimeWiringConfigurer unionConfigurer() {
return builder -> builder
.type("RemoveCustomerResult", typeWiring -> typeWiring
.typeResolver(env -> {
Object obj = env.getObject();
if (obj instanceof RemoveCustomerSuccess) {
return env.getSchema().getObjectType("RemoveCustomerSuccess");
} else if (obj instanceof ErrorResponse) {
return env.getSchema().getObjectType("ErrorResponse");
}
return null;
})
);
}
}
This configuration registers a resolver for the RemoveCustomerResult union type. At runtime, GraphQL uses this logic to determine whether to treat the result as a RemoveCustomerSuccess or ErrorResponse based on the Java object’s class.
The CustomerController Class
This controller contains the query and mutation logic for customer operations.
@Controller
class CustomerController {
private static final Map<Integer, Customer> customers = new HashMap<>(Map.of(
1, new Customer(1, "John Gibbs"),
2, new Customer(2, "Fred Joe"),
3, new Customer(3, "Thomas Rice"),
4, new Customer(4, "James Brown")
));
@QueryMapping
public List<Customer> allCustomers() {
return new ArrayList<>(customers.values());
}
@SchemaMapping(typeName = "Mutation", field = "removeCustomer")
public Object removeCustomer(@Argument Integer id) {
Customer removed = customers.remove(id);
if (removed != null) {
return new RemoveCustomerSuccess("Customer deleted successfully", id);
} else {
return new ErrorResponse("NOT_FOUND", "Customer with ID " + id + " does not exist");
}
}
}
This controller defines the GraphQL resolver methods. The removeCustomer mutation checks whether a customer exists. If found, it removes the customer and returns a success object; otherwise, it returns an error response. The return type is Object so it can hold either class, which is resolved using the union type logic defined earlier.
application.yml Configuration
spring:
graphql:
graphiql:
enabled: true
This configuration enables the GraphiQL interface, allowing developers to interact with and test GraphQL queries and mutations via a web browser at /graphiql.
Example GraphQL Mutation Call
Start the application, navigate to http://localhost:8080/graphiql, enter the query in the editor, and click the play button at the top of the window to execute it. You can test the mutation using the following query in the GraphiQL interface:
mutation {
removeCustomer(id: 1) {
... on RemoveCustomerSuccess {
message
deletedCustomerId
}
... on ErrorResponse {
errorCode
message
}
}
}
You will receive a response similar to this.
Or Error:
5. Conclusion
In this article, we demonstrated how to implement a GraphQL mutation in Java that can return different response types using a union type. By defining separate classes for success and error responses and configuring a type resolver in Spring Boot, we created a flexible mutation that communicates meaningful results to the client. This pattern is useful in applications where mutations may lead to different execution paths, providing a clean way to handle such cases.
6. Download the Source Code
This article covered how to return anydata from a GraphQL mutation in Java.
You can download the full source code of this example here: java return anydata graphql mutation






