Beyond REST: Rethinking APIs with GraphQL and Java

Vipin Menon

Introduktion

Distributed systems need little introduction today. They form the backbone of modern software architectures, enabling independent components to communicate, coordinate, and share resources seamlessly at scale. With the industry’s shift toward microservices, the importance of distributed systems has grown exponentially. In this landscape, APIs have emerged as the primary language of communication. APIs follow different implementation patterns. It is essential to think beyond simple request–response models and REST most of the time. For any API, “know your consumer” is the base principle. This blog explores a real-world use case and explains why it requires rethinking traditional request–response patterns and REST-based designs.

Rest and Beyond

REST: Simple, Stateless and Everywhere

REST has been the undisputed champion for building web APIs. Its simplicity, statelessness and standard HTTP methods baseline make it the go to architecture even in today’s data intensive world. REST treats everything as a resource, identifies each resource using a URI, and operates on it using standard HTTP methods.

REST architecture with its components

GraphQL: Flexible, Typed and Client-Driven

GraphQL, developed by Facebook in 2012 and open-sourced in 2015 is a query language for APIs. People describe it as the SQL for APIs. The main advantage of GraphQL over REST is that you ask for what data you want and you get exactly that. GraphQL provides a single endpoint. It maintains a strong contract between the client and server through its schema.

GraphQL architecture with its components

Rethinking APIs for AI Agents: The GraphQL Advantage

When building AI agents and tools one critical decision is choosing between REST and GraphQL. Requests are often unstructured and expressed in plain text. LLMs enable agents to understand intent and map these requests to the appropriate APIs that can fulfill them. REST can be challenging where we need to involve multiple endpoints. Let’s examine a real-world scenario in which conversational agents and bots process a plain-text query and produce a structured, accurate response.

Assume we have an application that contains organizations, employees and their car information.

The Example Query

“Give me the name of the employee who works for IBM and owns a Civic model car

REST

With REST, this requires

  • Finding employees who work at Organization “IBM”
  • Filtering those who own a Honda civic car
  • Returning only the name of the employee

For easy consumption, lets assume we have a specialized endpoint that has some advanced search functionalities that can accept organization name and car information in a single request.

Request:
GET /api/search?organizationName=IBM&carModel=Civic

Response:
{
  "employees": [{
    "id": "1",
    "firstName": "Tom",
    "lastName": "Bob",
    "email": "tom.bob@ibm.com",
    "position": "Software Engineer",
    "organizationId": "1"
  }],
  "cars": [{
    "id": "1",
    "model": "Civic",
    "brand": "Honda",
    "employeeId": "1"
  }],
  "organizations": [{
    "id": "1",
    "name": "IBM Corp",
    "address": "123 Business St"
  }]
}

If a single request that can handle this scenario is not available then we will end up involving multiple endpoints to get the desired results. Despite, of having a specific endpoint that can cover this scenario, lets see to the challenges that we can find in this particular use case.

  • Over fetching of data(Like address, email id, position) and this could lead to an overhead of processing the data before it can be served to the user.
  • Requires custom endpoints like above for every pattern.

GRAPHQL

With GraphQL, we can easily get rid of supporting multiple endpoints to cover use case as it deals with a single endpoint.

Schema:

type Query {
  advancedSearch(filters: SearchFilters): SearchResult!
}

input SearchFilters {
  carModel: String
  organizationName: String
  # ... other filters
}

type SearchResult {
  cars: [Car!]!
  employees: [Employee!]!
  organizations: [Organization!]!
}

type Employee {
  id: ID!
  firstName: String!
  lastName: String!
  email: String!
  position: String
  organization: Organization!
  car: Car
}

The above schema defines the data model of the underlying resources, making it strongly typed and establishing a clear contract between the client and the server. Each field that represents a complex type is mapped to an underlying resolver implementation. In this blog, the resolvers are implemented using the Java-based GraphQL-Java library. The same approach can be applied using any framework or language

Query:
query AdvancedSearch {
   advancedSearch(filters: { carModel: "Civic", organizationName: "IBM" }) {
        employees {
            firstName
            lastName
        }
    }
}

Response:
{
    "data": {
        "advancedSearch": {
            "employees": [
                {
                    "firstName": "Tom",
                    "lastName": "Bob"
                }
            ]
        }
    }
}

As shown in the example above, the response remains crisp, brief, and exactly to the request. This brings in advantage for an LLM to consume the response without any further processing. Here, GraphQL offers significant advantages.

  • It Handles complex, highly interconnected data without any need to fetch it from multiple API endpoints.
  • It returns simple responses tailored exactly to the request. This avoids over fetching and under fetching.
  • Supports a wide variety of complex query patterns from a single domain model.
  • Enables seamless mapping from natural language to API requests as the data maintains a full relationship with the domain model.

CONCLUSION

This blog uses a specific use case to demonstrate scenarios where GraphQL can outperform REST. It does not suggest that GraphQL is a replacement for REST. The two are complementary technologies, each suited to different problem spaces. Architectural decisions should be guided by a clear understanding of the consumer and the problem being solved. REST continues to be highly effective for use cases such as file handling and scenarios with identical responses where HTTP-level caching is critical.

This article is part of the JAVAPRO magazine issue:

From Coder To System Designer

Understand what it means to move from coding to designing systems in the age of AI.
Take a closer look at modern Java platforms, architectural thinking, and the responsibilities that come with shaping complex software systems.

Discover the edition 

Total
0
Shares
Previous Post

Code at Court – Java as evidence

Next Post

Building AI-Driven Java Systems with Spring AI and Java 26

Related Posts