Schema discovery
Services publish GraphQL schema locations and ownership metadata so platform components can discover them dynamically.
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.
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.
Services publish GraphQL schema locations and ownership metadata so platform components can discover them dynamically.
A GraphQL gateway reads registered schema metadata and exposes a single endpoint to clients.
Linked fields, downstream queries, scalar normalization, and batched execution are handled by the gateway through metadata rather than resolver-specific code.
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.
Product, catalog, inventory, and other services keep their own schemas and resolver logic. Federation does not require collapsing domain boundaries into a central monolith.
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.
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.
Each service exposes its own GraphQL schema and resolvers.
Schema locations and ownership metadata are published into the Registry.
The gateway reads registered schema locations and builds a composed executable schema.
Clients interact with a single GraphQL endpoint instead of multiple service-specific endpoints.
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.
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.
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.
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
)
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);
}
Linked field definitions are built during startup from annotated classes and methods.
A custom data fetcher resolves linked fields by building remote execution inputs from metadata.
Requested nested fields and inline fragments are preserved when rendering the downstream query.
Remote data is normalized and merged back into the final response returned to the client.
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.
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.
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.
Batched execution is declared through @GraphQLLink and
@GraphQLLinkArgument, not through imperative resolver code.
Pending linked field resolutions are collected during execution and grouped within the same request.
Services expose normal GraphQL queries such as productsByIds.
The batching strategy lives in the gateway execution layer.
graphql:
gateway:
batching:
enabled: ${GRAPHQL_GATEWAY_BATCHING_ENABLED:true}
@GraphQLLink(
schema = "product",
type = "Product",
query = "productsByIds",
collection = true,
batched = true,
arguments = {
@GraphQLLinkArgument(
name = "ids",
targetFieldName = "id",
batch = true
)
}
)
Resolvers register pending batched requests while the query executes.
The gateway groups item identifiers across multiple parent objects.
A single downstream GraphQL request is sent to the owning service.
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.
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.
The request hits the unified GraphQL endpoint exposed by the gateway.
The gateway identifies that a field is backed by remote link metadata and may participate in batching.
Links are either executed immediately or accumulated into the request-scoped batch registry.
The downstream response is normalized and merged into the original client response.
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.
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.
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.
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.
Pending batchable links are stored for the lifetime of the GraphQL request.
The gateway separates logical requests from individual returned items for correct reconstruction.
Dispatch can happen both for direct linked fields and for list children containing linked fields.
Batching can be enabled or disabled at runtime to compare execution paths or troubleshoot behavior.
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.
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.
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.
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.
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"
}
}
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.
A new schema becomes discoverable after registration.
Changes can be reflected by reloading or rebuilding the composed schema.
Joining or leaving nodes can affect where a schema is served from.
Composition logic can remain platform-native while still being replaceable.
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 graphql-gateway module as the central entry point when you want an opinionated
platform-native implementation.
Keep Spring Middleware as the metadata and discovery layer while implementing custom federation, execution, or query planning behavior on top.
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.