skip to content

How do you implement a federated subgraph in Spring for GraphQL, including entity resolution?

level: seniorimportance: must knowfreq 50%

answer

  1. FederationSchemaFactory + schemaFactory(...) on builder
  2. @link import @key in SDL
  3. @EntityMapping = _entities resolver
  4. @Argument binds key fields; or take Map
  5. @SchemaMapping/@BatchMapping for contributed fields

basics

~20 s

Add Spring for GraphQL's federation support, register a FederationSchemaFactory bean and wire it into the GraphQlSource builder, mark your type with @key in the schema, and add a controller method annotated @EntityMapping that returns the entity for a given key.

solid answer

~40 s

Spring for GraphQL ships federation support in the `org.springframework.graphql.data.federation` package. You register a `FederationSchemaFactory` bean and plug it into schema creation via a `GraphQlSourceBuilderCustomizer` calling `builder.schemaFactory(factory::createGraphQLSchema)` — this transforms your schema into a federated one, adding `_service`, `_entities`, `_Any`, and `_Entity`. In your `.graphqls` schema you add the federation `@link` import and mark entities with `@key(fields: "id")`. For each entity type you write a controller method annotated `@EntityMapping`; its parameters bind to the key fields via `@Argument`, and it returns the entity (or `Mono`). Spring dispatches each `_entities` representation to the matching method by `__typename` and preserves ordering. Fields contributed to a federated type resolve through ordinary `@SchemaMapping`/`@BatchMapping` methods. You still run Apollo Router externally to compose and route.

code

java · 28 lines
java
@Configuration
class FederationConfig {
  @Bean
  FederationSchemaFactory federationSchemaFactory() {
    return new FederationSchemaFactory();
  }

  @Bean
  GraphQlSourceBuilderCustomizer customizer(FederationSchemaFactory factory) {
    return builder -> builder.schemaFactory(factory::createGraphQLSchema);
  }
}

@Controller
class BookController {

  // Answers _entities lookups for the Book @key type
  @EntityMapping
  Book book(@Argument String id) {
    return repository.findById(id).orElse(null);
  }

  // Contributed / nested field on the federated Book type
  @SchemaMapping
  Author author(Book book) {
    return authorService.forBook(book.getId());
  }
}

go deeper

for a junior

Recognize @EntityMapping exists and @key marks the entity; not expected to wire the schema factory.

for a middle

Wire FederationSchemaFactory and write a single-entity @EntityMapping with @Argument.

for a senior

Handle composite keys via Map, batch entity resolution, and split contributed fields into @SchemaMapping/@BatchMapping.

for a principal

Design the subgraph boundary, key compatibility across teams, and the composition/router pipeline around Spring.

## Dependencies Spring for GraphQL's federation lives in `spring-graphql` under `org.springframework.graphql.data.federation`, and relies on the `federation-graphql-java-support` library from Apollo (pulled transitively). You need `spring-boot-starter-graphql` plus that federation support on the classpath. ## Step 1 — enable federation in the schema factory The subgraph must expose the federation types. You do this with `FederationSchemaFactory`: ```java @Configuration class FederationConfig { @Bean FederationSchemaFactory federationSchemaFactory() { return new FederationSchemaFactory(); } @Bean GraphQlSourceBuilderCustomizer customizer(FederationSchemaFactory factory) { return builder -> builder.schemaFactory(factory::createGraphQLSchema); } } ``` `schemaFactory(...)` replaces the default schema construction so the factory can inject `_service`, `_entities`, `_Any`, `_Entity`, and read your `@key` directives. ## Step 2 — declare the entity in SDL Federation v2 requires linking the spec and marking the entity: ```graphql extend schema @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key"]) type Book @key(fields: "id") { id: ID! title: String! } ``` `@key(fields: "id")` tells the router that `id` uniquely identifies a `Book`, so it can send `{__typename:"Book", id:...}` references to `_entities`. ## Step 3 — resolve entity references with @EntityMapping ```java @Controller class BookController { @EntityMapping Book book(@Argument String id) { return repository.findById(id).orElse(null); } @SchemaMapping Author author(Book book) { return authorService.forBook(book); } } ``` - `@EntityMapping` marks the method that answers `_entities` lookups for `Book`. Spring matches on `__typename` = `Book`. - `@Argument String id` binds the `id` key field from the representation. You can instead take the raw `Map<String,Object> representation` if the key is composite or dynamic. - Return the entity, or `Mono<Book>` / `CompletableFuture<Book>` for async, or `null` if not found. - Additional/contributed fields (like `author`) use normal `@SchemaMapping`. ## Batching to avoid N+1 If many `Book` references arrive in one request, per-representation lookups become N+1. `@EntityMapping` supports a batch form: declare the method to take a `List` of key arguments / representations and return a `List` (or `Flux`) in matching order. Alternatively resolve contributed collection fields with `@BatchMapping`. ## What Spring does NOT do - It is only the subgraph. **Composition** (`rover supergraph compose` / Apollo GraphOS) and the **router** run separately. - It does not invent keys — you must declare `@key` and ensure the key fields are actually resolvable. ## Common gotchas - Forgetting the `@link` import makes it a federation-v1-style or invalid subgraph; v2 tooling expects the link. - Returning entities in the wrong order from a batch `@EntityMapping` corrupts results silently — order must mirror the input. - The `@EntityMapping` method must be able to build the entity from the key alone (that is all the router provides), so don't depend on other request fields. - If two subgraphs share a `@key` type, their key fields must be compatible or composition fails.

  • How does Spring know which @EntityMapping method to call for a given representation?
    It dispatches by the representation's `__typename`. The method whose returned entity type matches (e.g. Book) is chosen, and key fields bind to its @Argument parameters.
  • Can an @EntityMapping method be reactive?
    Yes. It can return Mono<T> (or CompletableFuture<T>), and for batching a Flux<T>/List<T> resolved in input order.

context