Enterprise Java

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.

Java GraphQL Mutation Example Returning Multiple Data Types – Screenshot Output

Or Error:

Java GraphQL mutation example returning multiple data types – screenshot showing error output.

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.

Download
You can download the full source code of this example here: java return anydata graphql mutation

Omozegie Aziegbe

Omos Aziegbe is a technical writer and web/application developer with a BSc in Computer Science and Software Engineering from the University of Bedfordshire. Specializing in Java enterprise applications with the Jakarta EE framework, Omos also works with HTML5, CSS, and JavaScript for web development. As a freelance web developer, Omos combines technical expertise with research and writing on topics such as software engineering, programming, web application development, computer science, and technology.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button