Resolving Field Type Mismatch Composition Errors
When two subgraphs define the same field with incompatible type signatures, rover supergraph compose refuses to build the supergraph and emits a FIELD_TYPE_MISMATCH error — this guide walks through the exact messages, the rules Federation enforces, and how to align the SDL so composition succeeds.
This page is part of the wider discussion of Resolving Schema Conflicts in Apollo Federation, and focuses specifically on the type-mismatch and nullability-drift family of composition failures.
When to use this pattern
rover supergraph composefails withFIELD_TYPE_MISMATCHor anINVALID_FIELD_SHARING/ nullability error after two teams independently defined a shared type.- A field returns
String!in one subgraph andStringin another, or[Tag]versus[Tag!]!, and composition will not proceed. - You are introducing a
@shareablefield and need its signature to match exactly across every subgraph that declares it.
Prerequisites
The exact error messages
Composition validates that every subgraph contributing a field agrees on a compatible type. When they do not, you will see one of these:
Error: FIELD_TYPE_MISMATCH
Type of field "Product.weight" is incompatible across subgraphs:
it has type "Float!" in subgraph "inventory" but type "Int!" in subgraph "catalog"
Error: FIELD_TYPE_MISMATCH
Type of field "User.displayName" is incompatible across subgraphs:
it has type "String" in subgraph "accounts" but type "String!" in subgraph "profile"
Error: FIELD_ARGUMENT_TYPE_MISMATCH
Type of argument "Query.search(filter:)" is incompatible across subgraphs:
it has type "SearchInput!" in subgraph "search" but type "SearchInput" in subgraph "legacy-search"
The first is a true type mismatch — Float! versus Int! are different scalars and have no common ground; this one is a hard error you must resolve by changing one side. The second is nullability drift — String versus String! — which composition handles with a defined rule rather than rejecting outright in some cases. The third applies the same logic to argument types, where the nullability rule is inverted.
Why Federation requires compatible type signatures
The router builds a single supergraph schema that clients query against. For a field that more than one subgraph can resolve, the router must publish one type to clients and must be able to route a response from either subgraph into that single declared type without lying to the client. That constraint produces the compatibility rules.
For output (return) types, the supergraph publishes the most restrictive nullability that is safe. If subgraph A returns String! (never null) and subgraph B returns String (may be null), the router cannot promise clients non-null, because B might return null. Federation therefore composes the field as the nullable String — the weaker guarantee that both subgraphs can honour. The mismatch is allowed for nullability because there is a safe merge; it is not allowed for the underlying named type (Int vs Float), because no single type can represent both.
For input types and arguments, the rule inverts. An argument the router forwards must satisfy every subgraph that receives it. If one subgraph requires SearchInput! and another accepts SearchInput, the router composes the argument as the nullable form only when omitting it is safe for all consumers; where a subgraph demands non-null, the merged argument must be non-null. Practically: required-ness on inputs composes toward what all subgraphs can accept, which is why SearchInput! vs SearchInput surfaces as an error when the nullable side would break the strict subgraph.
Nullability and list-type rules
Lists add a second axis: both the list itself and its element type carry nullability. [Tag], [Tag!], [Tag]!, and [Tag!]! are four distinct types. Federation compares them position by position:
| Subgraph A | Subgraph B | Composes? | Result / reason |
|---|---|---|---|
String! |
String |
Yes (output) | Composes as String — nullable wins for outputs |
String |
String! |
Yes (output) | Composes as String |
Int! |
Float! |
No | FIELD_TYPE_MISMATCH — different named types |
[Tag!]! |
[Tag] |
Yes (output) | Composes as [Tag] — nullable list and elements |
[Tag!]! |
[Tag!]! |
Yes | Identical, no merge needed |
[Tag] |
[Product] |
No | FIELD_TYPE_MISMATCH — different element types |
SearchInput |
SearchInput! (arg) |
No | FIELD_ARGUMENT_TYPE_MISMATCH — input non-null cannot be satisfied |
The takeaway: nullability differences on output fields and list wrappers compose silently toward the nullable form, but a difference in the named type (the scalar, object, enum, or input at the core of the signature) is always a hard failure. The cleanest fix is almost never to rely on the silent nullable-merge — it weakens your client contract — but to make the signatures identical on purpose.
Fixing with aligned SDL
The reliable fix is to converge both subgraphs on a single signature. Below is a Product.weight conflict before and after. The owning subgraph (inventory) holds the authoritative definition; catalog marks the field @shareable because it also resolves it.
Before — composition fails with FIELD_TYPE_MISMATCH (Float! vs Int!):
# --- inventory subgraph (owner) ---
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.9",
import: ["@key", "@shareable"])
type Product @key(fields: "id") {
id: ID!
weight: Float! @shareable # grams, as a float
}
# --- catalog subgraph ---
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.9",
import: ["@key", "@shareable"])
type Product @key(fields: "id") {
id: ID!
weight: Int! @shareable # MISMATCH: Int! cannot merge with Float!
}
After — both subgraphs agree on Float!; composition succeeds:
# --- inventory subgraph (owner) ---
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.9",
import: ["@key", "@shareable"])
type Product @key(fields: "id") {
id: ID!
weight: Float! @shareable # canonical signature
}
# --- catalog subgraph ---
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.9",
import: ["@key", "@shareable"])
type Product @key(fields: "id") {
id: ID!
weight: Float! @shareable # now identical: Float! == Float!
}
Note that a shared field must be marked @shareable in every subgraph that defines it, with matching signatures. If catalog resolves weight but omits @shareable, composition fails differently — with INVALID_FIELD_SHARING — because Federation assumes single ownership unless told otherwise. The same alignment principle applies to nullability drift: pick the contract you actually want (usually the stricter String!) and set it identically in both subgraphs, rather than letting composition silently downgrade to String.
Fixing with @inaccessible
Sometimes the two definitions cannot be reconciled because they mean genuinely different things, or one subgraph’s field is legacy and should not reach clients. In that case, hide the offending field from the supergraph with @inaccessible. The field stays resolvable internally (so existing subgraph-internal references and @requires keep working) but is excluded from the API schema clients see — and excluded from the type-compatibility check on the public surface.
# --- legacy-search subgraph ---
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.9",
import: ["@key", "@inaccessible"])
type Product @key(fields: "id") {
id: ID!
# Legacy Int weight kept for internal computations but hidden from clients,
# so it no longer conflicts with inventory's Float! weight in the API schema.
weight: Int! @inaccessible
}
@inaccessible is the right tool when you are mid-migration: hide the divergent field, ship a composing supergraph today, and remove or realign the field on the subgraph’s own deployment cadence. It is not a substitute for fixing a genuine shared-field contract that clients need — for that, align the SDL as shown above.
Verifying with rover supergraph compose
Reproduce the failure and confirm the fix locally before publishing. Point supergraph.yaml at both subgraph SDLs and compose:
# supergraph.yaml lists each subgraph and its schema file/endpoint
rover supergraph compose --config supergraph.yaml > supergraph.graphql
A failing run prints the FIELD_TYPE_MISMATCH block and exits non-zero. After aligning the SDL, the same command exits zero and writes supergraph.graphql. In CI, gate merges with a per-subgraph check so a mismatch is caught against the published graph before it ever reaches composition — see Federated Schema Validation in CI/CD Pipelines:
# Catch the mismatch at PR time, before publishing
rover subgraph check my-graph@prod \
--schema ./catalog.graphql \
--name catalog
Common mistakes and gotchas
Trusting the silent nullability merge. Composition will happily downgrade String! to String to make a field compose, weakening your client contract without an error. Treat nullability drift as a real conflict and align both subgraphs on the strictness you intend.
Forgetting @shareable on one side. Matching types are not enough — every subgraph that resolves a shared, non-@key field must declare @shareable. Omitting it yields INVALID_FIELD_SHARING, a related but distinct error.
Hiding a needed field with @inaccessible. @inaccessible removes the field from the client-facing schema entirely. If clients query it, you will trade a composition error for a runtime Cannot query field error. Use it only for genuinely internal or migrating fields.
Frequently Asked Questions
What is the difference between FIELD_TYPE_MISMATCH and INVALID_FIELD_SHARING?
FIELD_TYPE_MISMATCH means two subgraphs declare the same field with incompatible type signatures (for example Int! vs Float!). INVALID_FIELD_SHARING means the signatures may be fine but the field is resolved by more than one subgraph without being marked @shareable, so Federation cannot tell which subgraph owns it. The first is fixed by aligning types; the second by adding @shareable to every definition.
Does a nullability difference like String vs String! always fail composition?
Not for output fields — Federation composes them as the nullable form (String) because that is the only guarantee both subgraphs can honour, so it often does not hard-fail. For input arguments the rule inverts and a required-vs-optional difference can fail. Either way, relying on the silent merge weakens your contract; align the nullability deliberately.
Can I use @inaccessible to make any composition error go away?
Only mismatches on fields you are willing to remove from the public API. @inaccessible excludes the field from the client-facing schema, so it resolves the conflict by hiding one side. If clients need the field, you must reconcile the type signatures instead.