How do you implement a federated subgraph in Spring for GraphQL, including entity resolution?
answer
- FederationSchemaFactory + schemaFactory(...) on builder
- @link import @key in SDL
- @EntityMapping = _entities resolver
- @Argument binds key fields; or take Map
- @SchemaMapping/@BatchMapping for contributed fields
basics
~20 sAdd 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 sSpring 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@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
Recognize @EntityMapping exists and @key marks the entity; not expected to wire the schema factory.
Wire FederationSchemaFactory and write a single-entity @EntityMapping with @Argument.
Handle composite keys via Map, batch entity resolution, and split contributed fields into @SchemaMapping/@BatchMapping.
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.