GraphQL Federation

GraphQL federation and batching for Spring Boot microservices

Spring Middleware provides a registry-driven GraphQL platform for Spring Boot microservices. Each service owns its schema, registers it in the Registry, and participates in a unified GraphQL API composed at runtime through a gateway.

The platform also enables declarative service communication, distributed GraphQL federation, and automatic batching of cross-service queries—solving N+1 problems at the platform level instead of resolver level.

What it is

Foundational infrastructure for federated GraphQL

Spring Middleware does not treat GraphQL as a local framework feature inside a single service. It provides the platform-level pieces required to support distributed GraphQL across multiple services: schema discovery, registry-backed metadata, gateway composition, centralized error handling, cross-service field resolution, and declarative batching of linked queries.

Schema discovery

Services publish GraphQL schema locations and ownership metadata so platform components can discover them dynamically.

Gateway composition

A GraphQL gateway reads registered schema metadata and exposes a single endpoint to clients.

Platform-level execution

Linked fields, downstream queries, scalar normalization, and batched execution are handled by the gateway through metadata rather than resolver-specific code.

Why it exists

One GraphQL API without coupling services together

In a microservice architecture, each service should remain autonomous: own its schema, own its resolvers, and evolve independently. At the same time, clients often need a single GraphQL entry point. Spring Middleware bridges that gap by making schemas discoverable and composable through the platform.

Keep service ownership explicit

Product, catalog, inventory, and other services keep their own schemas and resolver logic. Federation does not require collapsing domain boundaries into a central monolith.

Standardize the execution layer

Registry metadata, gateway behavior, error handling, remote execution patterns, and batch dispatch are standardized so teams do not have to rebuild GraphQL plumbing for every service landscape.

How it works

Registry-driven GraphQL topology

The Registry acts as the control plane for GraphQL metadata. Services register their schema locations, and the gateway uses that information to discover, load, and compose schemas dynamically.

GraphQL Gateway product-service catalog-service inventory-service

1. Service schema

Each service exposes its own GraphQL schema and resolvers.

2. Metadata registration

Schema locations and ownership metadata are published into the Registry.

3. Gateway discovery

The gateway reads registered schema locations and builds a composed executable schema.

4. Unified endpoint

Clients interact with a single GraphQL endpoint instead of multiple service-specific endpoints.

Registry metadata

SchemaLocation as platform metadata

GraphQL federation starts with reliable metadata. Spring Middleware stores schema information in the Registry so the gateway can discover where schemas live and which nodes expose them. Read more about the Registry model.

SchemaLocation tracks

  • Namespace of the schema
  • Schema definition location
  • HTTP context path
  • GraphQL API path
  • Nodes exposing the schema

What that enables

  • Runtime discovery of participating schemas
  • Dynamic gateway composition
  • Topology-aware federation
  • Gateway replacement or extension without changing services

Spring Middleware focuses on schema metadata, discovery, composition, and execution infrastructure. It is designed to make distributed GraphQL explicit and platform-native rather than hide it behind local resolver conventions.

Cross-service resolution

GraphQL Links

GraphQL Links allow a field in one service schema to be resolved by a query owned by another service. The platform collects metadata from annotations, and the gateway uses that metadata to build and execute remote GraphQL queries while preserving the selection set requested by the client.

Links can also combine batched and non-batched arguments. For example, a linked field can batch product identifiers through ids while still forwarding regular query parameters such as sort, page, or filters to the downstream service. This allows batched cross-service resolution without losing client-provided query semantics.

Simple argument mapping

Use a field value directly as a single remote argument. This is the most direct model for IDs or lists of IDs.

@GraphQLLink(
    schema = "product",
    type = "Product",
    query = "productsByIds",
    arguments = {
        @GraphQLLinkArgument(name = "ids")
    },
    collection = true
)

Multi-argument mapping

Use a method that returns a map-like structure when a linked field requires multiple target arguments.

@GraphQLLink(
    schema = "product",
    type = "Product",
    query = "productsByIds",
    collection = true,
    batched = true,
    arguments = {
        @GraphQLLinkArgument(
            name = "ids",
            targetFieldName = "id",
            batch = true
        ),
        @GraphQLLinkArgument(name = "sort")
    }
)

@GraphQLQuery(name = "products")
public GraphQLLinkArguments getProductIds(
        @GraphQLArgument(name = "sort") String sort
) {
    Map<String, Object> args = new HashMap<>();
    args.put("ids", productIds);

    if (sort != null) {
        args.put("sort", sort);
    }

    return new GraphQLLinkArguments(args);
}

Metadata extraction

Linked field definitions are built during startup from annotated classes and methods.

Gateway resolution

A custom data fetcher resolves linked fields by building remote execution inputs from metadata.

Selection preservation

Requested nested fields and inline fragments are preserved when rendering the downstream query.

Response composition

Remote data is normalized and merged back into the final response returned to the client.

Declarative batching

Solve distributed N+1 at platform level

One of the most advanced parts of Spring Middleware is declarative GraphQL batching. Instead of solving N+1 through resolver-specific code or manual DataLoader wiring, the gateway detects batched links from metadata and executes one aggregated downstream query for multiple pending field resolutions.

Live demo

Same query. 100 calls → 1 call.

This demo shows the difference between immediate linked-field execution and declarative batching at gateway level, reducing latency from 814 ms to 165 ms.

N+1 is solved at the platform level, not in resolvers. For a written deep dive, read the technical article on Medium.

Benchmark

20 VUs · 100 catalogs × 100 products × 10 reviews

The graph below compares the same GraphQL hierarchy with batching disabled and enabled. It shows both the reduction in downstream calls and the impact on latency and throughput.

GraphQL batching benchmark comparing no batching vs batching, including downstream calls, latency, and throughput
In this scenario, downstream review calls were reduced from 570,000 to 304, while average latency improved from 18.35s to 5.42s and throughput increased from 0.27 to ~1.06 req/s.

Metadata-driven

Batched execution is declared through @GraphQLLink and @GraphQLLinkArgument, not through imperative resolver code.

Request-scoped

Pending linked field resolutions are collected during execution and grouped within the same request.

Transparent for services

Services expose normal GraphQL queries such as productsByIds. The batching strategy lives in the gateway execution layer.

Enable batching

graphql:
  gateway:
    batching:
      enabled: ${GRAPHQL_GATEWAY_BATCHING_ENABLED:true}

Declare a batched link

@GraphQLLink(
    schema = "product",
    type = "Product",
    query = "productsByIds",
    collection = true,
    batched = true,
    arguments = {
        @GraphQLLinkArgument(
            name = "ids",
            targetFieldName = "id",
            batch = true
        )
    }
)
100 Catalog.products GraphQL Gateway batch registry + dispatch productsByIds(ids) without batching many remote calls one batched call

1. Linked fields accumulate

Resolvers register pending batched requests while the query executes.

2. IDs are aggregated

The gateway groups item identifiers across multiple parent objects.

3. One remote query runs

A single downstream GraphQL request is sent to the owning service.

4. Results are reconstructed

The gateway maps returned items back to the original parent fields transparently.

In sample scenarios, this changes execution from many remote GraphQL requests to a single batched request, with observed latency reductions from roughly ~1 second to roughly ~100–200 ms.

Execution model

How linked fields are resolved and dispatched

The gateway composes an executable schema and delegates linked fields to a remote execution path. When batching is enabled, the execution model changes from immediate per-field calls to request-scoped registration, grouped dispatch, and batched reconstruction.

The gateway also preserves GraphQL variables and client-provided arguments when rendering downstream federated queries, including batched links with mixed batched and non-batched parameters.

01

Client queries the gateway

The request hits the unified GraphQL endpoint exposed by the gateway.

02

Linked field metadata is detected

The gateway identifies that a field is backed by remote link metadata and may participate in batching.

03

Dispatch strategy is selected

Links are either executed immediately or accumulated into the request-scoped batch registry.

04

Remote results are normalized

The downstream response is normalized and merged into the original client response.

Execution view

At runtime, the gateway sits between the client and the target services, building and executing the downstream query path from link metadata. Batched links are coordinated through instrumentation and GraphQL context rather than custom resolver orchestration.

Client GraphQL Gateway analyze + batch + resolve + execute catalog-service product-service other schema
Batch dispatch internals

Instrumentation coordinates the batching flow

Declarative batching is implemented inside the gateway execution layer through dedicated instrumentation. This allows batching to remain aligned with the real query structure and selected fields instead of relying on static schema-wide assumptions.

GraphQLBatchInstrumentation

The batching layer detects safe dispatch points, coordinates request-scoped execution through GraphQL context, and prevents duplicate dispatch for the same parent field while a batch is in flight.

Selected-field aware dispatch

The gateway uses execution metadata such as ExecutionStepInfo and MergedField to inspect the actual query being executed and dispatch only when linked fields are really needed.

Request-scoped registry

Pending batchable links are stored for the lifetime of the GraphQL request.

BatchKey vs ItemKey

The gateway separates logical requests from individual returned items for correct reconstruction.

List completion support

Dispatch can happen both for direct linked fields and for list children containing linked fields.

Runtime toggle

Batching can be enabled or disabled at runtime to compare execution paths or troubleshoot behavior.

Type system concerns

Inline fragments and scalar normalization

Distributed GraphQL is not only about joining schemas. It also requires preserving type semantics across service boundaries. Spring Middleware is designed to support polymorphic responses and normalize scalar values so the unified API remains consistent.

Inline fragments

Polymorphic queries may include subtype-specific selections such as fields available only on concrete implementations. The gateway preserves inline fragments when rendering downstream queries, so type-specific data is still resolved correctly.

Scalar normalization

Different services may serialize values such as UUID, Instant, BigDecimal, or LocalDateTime differently. The gateway can normalize those values so responses remain aligned with the unified schema definition.

Error model

Consistent GraphQL errors across services

Spring Middleware provides centralized error handling for GraphQL endpoints inside services. The goal is to keep GraphQL responses aligned with the same domain error model already used across HTTP APIs. This ensures consistent error semantics across transport layers.

Handled categories

  • ServiceException
  • ErrorDescriptor
  • PersistenceException
  • ConstraintViolationException
  • Fallback unknown errors

Behavior

GraphQL errors use the same domain error codes as HTTP responses, exposed through extensions.code. This keeps error semantics stable across protocols.

{
  "message": "Product not found",
  "path": ["product"],
  "extensions": {
    "code": "PRODUCT:NOT_FOUND"
  }
}
Dynamic platform behavior

Composition can evolve with topology changes

Because schema metadata lives in the Registry, a gateway can rebuild its composed schema when the platform changes. This supports a more dynamic public API surface without forcing a manual gateway redeploy for every topology update.

New service

A new schema becomes discoverable after registration.

Schema update

Changes can be reflected by reloading or rebuilding the composed schema.

Node changes

Joining or leaving nodes can affect where a schema is served from.

Gateway flexibility

Composition logic can remain platform-native while still being replaceable.

Adoption path

Built-in gateway or custom gateway

Teams can adopt the built-in GraphQL gateway as a platform-native reference implementation, or they can build their own gateway that consumes the same registry metadata model.

Use the built-in gateway

Use the graphql-gateway module as the central entry point when you want an opinionated platform-native implementation.

Build your own gateway

Keep Spring Middleware as the metadata and discovery layer while implementing custom federation, execution, or query planning behavior on top.

Next steps

Explore the platform pieces behind GraphQL

GraphQL in Spring Middleware is part of a broader platform model built around registry-driven discovery, explicit service topology, declarative communication, and consistent infrastructure behavior.