releases.shpreview
Home/Apollo GraphQL
Apollo GraphQL

Apollo GraphQL

🐛 Fixes

Preserve null propagation when multiple fragments select the same non-null field (PR #9032)

When a query uses multiple fragment spreads on the same parent type and a subgraph response is missing a required non-null field on a union member, the router now correctly returns null for the affected field rather than a partial object like {"__typename": "A"}.

The GraphQL specification requires that a non-null violation propagates null upward to the nearest nullable parent. Previously, if one fragment nullified a field, a subsequent fragment on the same parent could overwrite that null with a partial result — producing a spec-incorrect response.

By @abernix in https://github.com/apollographql/router/pull/9032

Reject invalid values for client library name and version (PR #8934)

Rejects invalid values (validated against a regex) for library name and version provided in headers or operation extensions, which are used for the Client Awareness feature and telemetry.

By @BoD in https://github.com/apollographql/router/pull/8934

Only delete coprocessor context keys from those that were sent in a given stage (PR #9519)

Addresses a race condition where context keys added by concurrent parallel subgraph stages could unintentionally be deleted.

By @rohan-b99 in https://github.com/apollographql/router/pull/9519

Normalize supergraph.path to support queries with and without trailing slashes (/) (PR #8860)

Normalize trailing / for supergraph.path to support /graphql and /graphql/. This works by stripping trailing / from both the configured path and the incoming query path to ensure they match, regardless of whether the config or query includes a trailing slash.

By @Jephuff in https://github.com/apollographql/router/pull/8860

Today, we're launching a new API keys page in GraphOS Studio, a single place where you can view and manage your SCIM, subgraph, and operator API keys directly from the UI. With built-in tools to filter by key status, cleanly rotate expiring keys with configurable buffer periods, and revoke unused keys, it empowers platform teams to maintain strict security oversight over their graph infrastructure without disrupting active development workflows.

Figure 1: API keys page in GraphOS Studio

The problem it solves

API keys are the backbone of securing access to your GraphQL infrastructure. But managing them has historically been fragmented. Subgraph keys, operator keys, and SCIM keys lived in different places, and were only available through Rover CLI and the Platform API.

If a key was compromised or a token needed to be rotated out, your options were binary: delete it and lose the audit trail, or leave it active and accept the risk. Now, you can rotate or revoke a key so that it cannot be used, but you can continue to view it in your list of API keys.

What we built

View, search, and filter API keys at a glance

SCIM, subgraph, and operator keys are now visible in Apollo Studio. Search by name, filter by type or status, and quickly find the key you're looking for.

Figure 2: Filter API keys by type

Create, rename, and update expiration

Need a new API key for an upcoming deployment? As long as you have the appropriate permissions you can now spin one up without leaving the browser. You can also rename existing keys and adjust their expiration dates directly from the UI, keeping your key inventory clean and descriptive as your organization grows.

Figure 3: Create a new API key

Rotate API keys without downtime

The new rotate action lets you cycle out a key while keeping it accessible for investigation or a transition period, so you can maintain continuity without sacrificing oversight. No more choosing between security and uptime.

Figure 4: Rotate API key

Revoke vs. delete API key

Sometimes you need to immediately cut off access to a key, but you're not ready to erase it entirely. Revoking a key stops it from being used right away while preserving the record, useful for incident response, audits, or investigations where visibility matters. Deleting a key remains available when you're ready to permanently remove it.

Figure 5: Revoke API key

Getting started

Access to the API keys page is based on your role in GraphOS Studio. Graph admins can manage subgraph keys for their associated graphs. Org Admins have full access to all key types across the organization. Want to learn more? Check out our documentation for detailed setup instructions.

Rover CLI users, this doesn't change your workflow. Rover CLI continues to support creating, listing, deleting, and renaming subgraph and operator API keys. The Studio UI is an additive surface, not a replacement.

What's next

The API keys page is the foundation for a broader vision: a unified view of every key in your Apollo organization. Currently, graph and user API keys are available on separate key management pages, but in the future they will be available on the API keys page. You'll be able to see and manage personal, graph-level, and org-level keys side by side. We're also working on getting all of these new capabilities into Rover CLI.

🐛 Fixes

Support non-ASCII (UTF-8) WebSocket header values (Issue #1485, PR #9051)

The router can now handle WebSocket connections with UTF-8 encoded header values, including non-ASCII characters like "Montréal". Previously, such connections failed because of serialization issues in the underlying tungstenite library.

The fix comes from updating tokio-tungstenite from v0.28.0 to v0.29.0.

By @BobaFetters in https://github.com/apollographql/router/pull/9051

Handle both deprecated enum values when merging coprocessor context (PR #8913)

A change to coprocessor context merges in Router v2.10 caused keys to be deleted when context: true is used as the coprocessor context selector in the router configuration file.

The workaround was to pass context: deprecated instead. This change brings parity when context: true is provided.

By @carodewig in https://github.com/apollographql/router/pull/8913

🛠 Maintenance

Pin transitive h2 dependency at minimum v0.4.13 to pick up critical flow-control, deadlock, and tracing fixes (PR #9033)

h2 0.4.13 (released January 5, 2026) contains three fixes directly relevant to the router, which uses h2 exclusively as a client when connecting to subgraphs:

  • Capacity deadlock under concurrent streams (#860) — high relevance: Under concurrent load with max_concurrent_streams limits in effect, flow-control capacity could be assigned to streams still in pending_open state. Those streams could never consume the capacity, starving already-open streams and permanently freezing all outgoing traffic on the connection with no error surfaced. This is directly triggerable in the router: any subgraph behind Envoy or a gRPC backend advertises a max_concurrent_streams limit (Envoy defaults to 100), and under production load the router will routinely queue more concurrent requests than that limit allows.

  • OTel tracing span lifetime leak (#868) — high relevance: The h2 Connection object captured the active tracing span at connection creation time as its parent, keeping that span alive for the entire lifetime of the connection. Since the router wraps every subgraph request in an OpenTelemetry span and connections are pooled, affected spans could linger indefinitely under sustained traffic — never being exported to the tracing backend and accumulating in memory.

  • Flow-control stall on padded DATA frames (#869) — lower relevance for typical subgraphs, higher for connectors: Padding bytes in DATA frames were not being returned to the flow-control window, causing the connection window to drain to zero and permanently stalling downloads with no error. Typical GraphQL/gRPC subgraphs do not send padded frames, but router connectors calling arbitrary HTTP APIs (e.g., Google Cloud Storage or CDN-backed endpoints) can encounter this.

By @theJC in https://github.com/apollographql/router/pull/9033

🚀 Features

  • Add rover schema search subcommand - @dotdat PR #3315

    Wires the new rover schema search FILE TERMS... subcommand on top of the ParsedSchema::search engine added in PR #3262. Accept SDL from a file (or from stdin when FILE is -), render results as text or JSON via the standard CliOutput plumbing, and support --limit/-n and --include-deprecated.

🐛 Fixes

  • Preserve auth and other reqwest helpers in the retry tower layer - @SharkBaitDLS PR #3327 fixes #3326

    Rebuilds requests through reqwest::RequestBuilder inside the retry tower layer so the builder's helper logic (which extracts auth into headers, among other things) is preserved on retried requests. Previously the layer used reqwest::Request::try_from, which silently dropped those helpers. Also restricts retries to retriable HTTP status codes and skip gzip-decoding error responses so the underlying failure surfaces instead of manifesting as a hang.

  • Rewrite graph introspect to use apollo-compiler - @SharkBaitDLS PR #3317 fixes #3312

    Moves graph introspect off the deprecated apollo-encoder crate and onto apollo-compiler to pick up upstream SDL-encoding fixes that Rover had been missing.

  • Batch supergraph.yaml subgraph changes on hot reload - @SharkBaitDLS PR #3304

    Applies all subgraph additions and removals from a single supergraph.yaml edit as one batch before recomposing in rover dev. Previously each change was processed individually, so removing a subgraph whose fields were referenced via @external produced an intermediate composition failure that persisted as the final state without recovering.

  • Preserve --graph-ref subgraphs across hot reloads - @SharkBaitDLS PR #3288

    Re-merges remote --graph-ref subgraphs on every supergraph.yaml reload when rover dev is run with both --graph-ref and a local supergraph file. Previously the watcher only re-read the YAML and dropped the graph-ref-only subgraphs that had been merged in at startup.

  • Fix release tagging workflow - @SharkBaitDLS PR #3309

    Switches the release "refs exist" check to the exact-match GitHub tag API. Previously it used a fuzzy-matching API that incorrectly no-op'd when prior release-candidate tags existed. Also restores the original workflow names to preserve Marketplace URLs and SEO.

🛠 Maintenance

  • Retry artifact uploads in CI - @dotdat PR #3325

    Adds retries to actions/upload-artifact so transient network failures during CI uploads no longer fail builds.

  • Drop unused variant-name querying - @sirdodger PR #3320

    Removes the unused variants field from the graph query to improve performance for graphs with many variants.

  • Run cargo +nightly fmt --all at the end of mise run prep - @dotdat PR #3311

📚 Documentation

  • Add Docker image information to CI docs - @SharkBaitDLS PR #3318

    Documents the published Docker images in the CI/CD docs and aligns action names with the links already used on the docs site.


This release was automatically created by GitHub Actions.

If you would like to verify that the binary you have downloaded was built from the source code in this repository, you can compute a checksum of the zipped tarball and compare it to the checksums that are included as release artifacts.

Binaries built for MacOS are signed, notarized, and automatically verified with Gatekeeper.

We're excited to announce the release of Apollo Client 4.2. This release brings two additional long-awaited features:

  • Type-safe default options
  • Event-based refetching

Let's dive in!

Type-safe default options

Prior to version 4.2, you had to choose between convenience and type-safety when you wanted to propagate a default option throughout your application. For example, you might set your default errorPolicy to "all" in order to render partially successful queries in your components.

This could introduce a mismatch between the runtime behavior and what TypeScript reports as the right value. Consider this useSuspenseQuery example:

const { data } = useSuspenseQuery(QUERY);
//      ^? TData

const { data } = useSuspenseQuery(QUERY, { errorPolicy: "all" });
//      ^? TData | undefined

With no explicit errorPolicy, useSuspenseQuery's throws by default when an error is returned, which means data can be typed as the query data type. Passing errorPolicy: "all" changes the return type to include undefined because the server might return an error with no data.

Changing the default errorPolicy in defaultOptions was considered unsafe however because it modified the runtime behavior, but the types remained the same. This could cause crashes in your production environment that weren't caught by TypeScript because you might access properties on undefined.

In 4.2, default options are now propagated through all React hooks and APIs to provide the correct type to match the runtime value. You opt in by declaring your default options using TypeScript module augmentation:

// apollo.d.ts
import "@apollo/client";

declare module "@apollo/client" {
  namespace ApolloClient {
    namespace DeclareDefaultOptions {
      interface WatchQuery {
        errorPolicy: "all";
      }
    }
  }
}

To make sure the runtime behavior matches the types, Apollo Client forces you to add a matching defaultOptions option:

new ApolloClient({
  // without this option, TypeScript reports an error
  defaultOptions: {
    watchQuery: {
      errorPolicy: "all"
    }
  }
});

With that TypeScript declaration in place, the hook now reflects the runtime value:

const { data } = useSuspenseQuery(QUERY);
//      ^? TData | undefined

This behavior extends to all query and mutation hooks and core APIs.

Learn more about declaring type-safe default options in the TypeScript guide.

Deprecation of generic arguments

To achieve type-safe default options, Apollo Client requires the use of type inference. As a result, passing generic arguments to hooks and core APIs is now deprecated.

// Generic arguments are no deprecated
useQuery<DataType, VariablesType>(QUERY)

You can still use this signature, but you won't be able to take advantage of the new type-safety. Migrate to TypedDocumentNode instead:

const QUERY: TypedDocumentNode<DataType, VariablesType> = gql``

Learn more about migrating in the migration guide.

Event-based refetching

One of our most popular requests has been window focus refetching, a feature popularized by TanStack Query that triggers automatic refetches when the browser tab regains focus. Building your own system for handling automatic refetches resulted in a complicated mess of useEffect or wrapper hooks to provide this sort of functionality yourself.

4.2 introduces the new RefetchEventManager, which handles refetches for you in response to events such as window focus or network reconnection. Pass a RefetchEventManager instance to the refetchEventManager option to opt-into automatic refetches:

import { RefetchEventManager, windowFocusSource } from "@apollo/client";

const client = new ApolloClient({
  // ...
  refetchEventManager: new RefetchEventManager({
    sources: {
      windowFocus: windowFocusSource,
    },
  }),
});

Anytime a user focuses the browser tab, the client automatically refetches active queries.

Queries can also opt-out of a specific event refetch with the new refetchOn option:

useQuery(QUERY, { 
  // Don't refetch this query when the windowFocus event is triggered
  refetchOn: { windowFocus: false } 
});

RefetchEventManager is designed for extensibility in mind. You can register your own custom events, provide customized handlers to determine which queries should be refetched, and more.

See the event-based refetching docs to learn more.

Wrapping up

Ready to upgrade? Install Apollo Client 4.2 today:

npm install @apollo/client@latest

For the full list of changes, check out the release notes. Questions and feedback are always welcome in the Apollo Community.

Happy querying!

🚀 Features

Add ignore_auth_context option to subscription deduplication config (PR #9078)

When the router's JWT authentication plugin validates a token, it decodes the claims and stores them internally on the request — before any subgraph request is built. The router then factors those stored claims into its check for whether two subscriptions are identical, separately from any HTTP headers it may forward downstream.

This means that on any router with JWT authentication enabled, every authenticated user effectively gets their own subgraph WebSocket connection — even if the subscription data is identical for all users, and even if the Authorization header is never forwarded to the subgraph at all. Adding authorization to ignored_headers doesn't help here, because it only affects HTTP headers; the decoded claims live in a different layer that ignored_headers never touches.

Two new capabilities are added to the deduplication config block:

  • ignore_auth_context: bool (default: false) — when true, the router skips stored JWT claims when checking subscription identity, allowing all authenticated users to share a single subgraph WebSocket connection when the subscription data is truly non-personalized (e.g., product price updates, stock price feeds).
  • Per-subgraph deduplication control via all: / subgraphs: — deduplication settings can now be set globally with a default and overridden per subgraph by name, using the standard SubgraphConfiguration<T> pattern already used elsewhere in the router config.
subscription:
  deduplication:
    all:
      enabled: true
    subgraphs:
      stocks:
        ignore_auth_context: true

By @abernix in https://github.com/apollographql/router/pull/9078

Add include_cache_control_header_on_router_response to suppress Cache-Control on client responses (PR #9002)

The response cache plugin now supports a include_cache_control_header_on_router_response boolean config option (defaults to true). When set to false, the router omits the Cache-Control header from supergraph responses sent to clients, while all internal caching behavior — Redis storage, TTL enforcement, cache key computation, and the cache debugger — remains unchanged.

This is useful when the router sits behind a CDN or reverse proxy that manages its own caching headers, or when you want to prevent clients from caching responses locally while keeping server-side caching active.

response_cache:
  enabled: true
  include_cache_control_header_on_router_response: false  # default: true
  subgraph:
    all:
      enabled: true
      redis:
        urls: ["redis://..."]

By @ebylund in https://github.com/apollographql/router/pull/9002

Add per-subgraph and per-connector HTTP response size limits (PR #9160)

The router can now cap the number of bytes it reads from subgraph and connector HTTP response bodies, protecting against out-of-memory conditions when a downstream service returns an unexpectedly large payload.

The limit is enforced as the response body streams in — the router stops reading and returns a GraphQL error as soon as the limit is exceeded, without buffering the full body first.

Configure a global default and optional per-subgraph or per-source overrides:

limits:
  subgraph:
    all:
      http_max_response_size: 10MB # 10 MB for all subgraphs
    subgraphs:
      products:
        http_max_response_size: 20MB # 20 MB override for 'products'

  connector:
    all:
      http_max_response_size: 5MB # 5 MB for all connector sources
    sources:
      products.rest:
        http_max_response_size: 10MB # 10 MB override for 'products.rest'

There is no default limit; responses are unrestricted unless you configure this option.

When a response is aborted due to the limit, the router:

  • Returns a GraphQL error to the client with extension code SUBREQUEST_HTTP_ERROR
  • Increments the apollo.router.limits.subgraph_response_size.exceeded or apollo.router.limits.connector_response_size.exceeded counter
  • Records apollo.subgraph.response.aborted: "response_size_limit" or apollo.connector.response.aborted: "response_size_limit" on the relevant span

Configuration migration: Existing limits fields (previously at the top level of limits) are now nested under limits.router. A configuration migration is included that updates your config file automatically.

By @carodewig in https://github.com/apollographql/router/pull/9160

Add apollo.router.connection.acquire.duration metric for TCP/TLS connection timing (PR #9309)

Adds a new histogram metric, apollo.router.connection.acquire.duration, that records how long it takes to establish a new TCP or Unix socket connection to a downstream service (subgraph, connector, or coprocessor). The metric fires only when the connection pool opens a new connection — pool hits are not recorded.

This metric is useful for diagnosing connection establishment latency. For example, if a subgraph shows elevated overall response latency, a high connection.acquire.duration indicates the delay is in TCP/TLS setup; a near-zero value (or no data) points to post-connection causes like slow server responses.

Attributes:

  • network.transport: tcp for HTTP connections, unix for Unix socket connections
  • subgraph.name: name of the subgraph (for subgraph connections)
  • connector.source.name: name of the connector source (for connector connections)
  • coprocessor: true (for coprocessor connections)

By @carodewig in https://github.com/apollographql/router/pull/9309

Add max_lifetime configuration for subscriptions (PR #9216)

Adds a new max_lifetime field to the subscription configuration block, allowing operators to set a maximum duration for how long a subscription can remain open. After the configured duration the subscription is closed and the client receives a terminal error with extension code SUBSCRIPTION_MAX_LIFETIME_EXCEEDED.

subscription:
  enabled: true
  max_lifetime: 10m  # close subscriptions after 10 minutes
  mode:
    callback:
      public_url: "https://my-router.example.com/subscription/callback"

By default (max_lifetime unset) there is no lifetime limit, preserving the existing behaviour.

By @BobaFetters in https://github.com/apollographql/router/pull/9216

Add operation_body_timeout for file upload requests (PR #9243)

Adds a new operation_body_timeout limit to the file uploads plugin, allowing operators to set a tight deadline on reading the operations field (GraphQL query + variables) from multipart request bodies, independently of the overall router timeout.

File uploads is the only router flow where the request body is read as a stream in the plugin layer: the multipart body must be parsed to extract the operations field before query planning can begin. This means a slow or stalled client can hold a connection open until the global router timeout fires. The new operation_body_timeout lets you set a tighter deadline specifically for that body-reading phase.

If operation_body_timeout is not set, no additional body-read timeout is applied — the overall router timeout remains the only bound.

preview_file_uploads:
  enabled: true
  protocols:
    multipart:
      enabled: true
      limits:
        operation_body_timeout: 5s  # optional; no default

When the timeout fires, the router returns a 504 Gateway Timeout response with extension code GATEWAY_TIMEOUT.

By @carodewig in https://github.com/apollographql/router/pull/9243

🐛 Fixes

Resolve @connect field values when a root query alias is combined with field-level aliases (Issue #9347)

Queries that aliased both the root query field and one or more of its subfields on a @connect-backed type returned null for every aliased subfield, which could cascade into null propagation for non-nullable types. Either alias in isolation worked correctly — only the combination of both triggered the bug.

Given this query:

{
  items: search_items(query: "test") {
    results {
        id 
        link: viewUri 
    }
  }
}

Before

Aliased fields returned null, and null propagation bubbled up through non-nullable types until the entire result was nullified:

{
  "data": { "items": null },
  "extensions": {
    "valueCompletion": [
      { "message": "Null value found for non-nullable type String", "path": ["items", "results", 0] },
      { "message": "Null value found for non-nullable type Item", "path": ["items", "results", 0] },
      { "message": "Null value found for non-nullable type [Item!]", "path": ["items", "results"] }
    ]
  }
}

After

Root and field aliases now work together as expected:

{
  "data": {
    "items": {
      "results": [
        { "id": "1", "link": "https://example.com/docs/001" }
      ]
    }
  }
}

By @jhrldev in https://github.com/apollographql/router/pull/9358

Record apollo.router.operations.coprocessor.duration even when the coprocessor call times out (PR #9296)

apollo.router.operations.coprocessor.duration is now recorded even when a coprocessor call is cut short by a router timeout. Previously, the metric was only emitted when the call completed normally, leaving timeout latencies invisible in the histogram.

By @conwuegb and @carodewig in https://github.com/apollographql/router/pull/9296

Retry reloads automatically after transient failures instead of staying on a stale schema (PR #9391)

Previously, if a schema reload failed — for example, because a persisted query manifest fetch from Uplink encountered a transient network error — the router would log "error while reloading, continuing with previous configuration" and then stop retrying. All subsequent background polls from Uplink would return Unchanged (because last_id had already advanced to the new schema ID), leaving the router permanently serving the old schema until manually restarted.

The router now enters a Reloading state on reload failure and schedules automatic retries. The retry delay (default 10 seconds) and maximum retry count (default 5) are configurable via the new reload configuration key:

reload:
  max_retries: 5    # 0 to disable, null for unlimited
  retry_delay: 10s

The retry budget is reset whenever a new reload trigger arrives — a new schema or license from Uplink, a configuration or rhai script file change, or an explicit reload signal — so any new change always gets a fresh set of attempts even if previous retries were exhausted.

By @carodewig in https://github.com/apollographql/router/pull/9391

Scope SigV4 signing params to a single subgraph request instead of sharing them across the operation (PR #9385)

When authentication.subgraph.subgraphs configured aws_sig_v4 for a specific subgraph, the signing parameters were visible to every other subgraph in the same operation — causing unconfigured subgraphs to have their Authorization header overwritten with AWS credentials.

Signing parameters are now scoped to the individual subgraph HTTP request rather than the operation, so AWS credentials only travel with requests to the subgraph they were configured for.

By @carodewig in https://github.com/apollographql/router/pull/9385

Identify the primary @defer chunk correctly in the is_primary_response telemetry selector (PR #9238)

The is_primary_response: true supergraph telemetry selector returned false for every chunk of a multipart @defer response — including the primary (first) chunk — when evaluated at on_response or on_response_event scope. This made it impossible to distinguish primary from deferred chunks in metrics, events, and conditional telemetry.

The selector now returns true for the primary chunk and false for subsequent deferred chunks, so per-chunk filtering works as documented:

telemetry:
  instrumentation:
    instruments:
      supergraph:
        my.defer.primary.duration:
          value: event_duration
          type: histogram
          attributes:
            is_primary:
              is_primary_response: true

Now produces split metric series (is_primary="true" for the primary chunk, is_primary="false" for deferred chunks) instead of a single series with is_primary="false" for everything.

By @ebylund in https://github.com/apollographql/router/pull/9238

Evaluate on_graphql_error per response part in coprocessors and telemetry for @defer responses (PR #9365)

Previously, the on_graphql_error condition used in coprocessor and telemetry configurations did not work correctly for deferred (@defer) multipart responses:

  • The supergraph coprocessor evaluated the condition once before any stream chunks were consumed, so it never fired for errors that appeared only in incremental chunks.
  • The router coprocessor read the sticky CONTAINS_GRAPHQL_ERROR context flag, which caused it to fire for every subsequent chunk once any earlier chunk had errors — and to never fire if the first chunk was clean.
  • The on_graphql_error telemetry selector at the supergraph stage returned the accumulated error state rather than the current chunk's error state.

The router and supergraph coprocessors, along with telemetry selectors, now evaluate on_graphql_error conditions per response part, so the condition fires exactly once per part that contains GraphQL errors — no more, no less.

Additionally, on_graphql_error: false (fire when there are no errors) now works correctly in all selector contexts: router, supergraph, and subgraph.

By @carodewig in https://github.com/apollographql/router/pull/9365

Drop duplicate Rhai script watcher notifications when the change channel is full (PR #9391)

When many filesystem events arrived in quick succession, the Rhai script watcher could spin an OS thread or panic — the previous retry loop kept trying to send on a full channel, and would panic if the receiver closed before the retry succeeded.

The watcher now drops duplicate notifications when the channel is already full, matching the behavior introduced for the configuration file watcher in PR #8336. Reloads always re-read the current file from disk, so a single pending notification in the channel is sufficient to guarantee the latest contents will be picked up.

By @carodewig in https://github.com/apollographql/router/pull/9391

🛠 Maintenance

Make batching.mode optional and reject unknown subgraph batching fields (PR #9315)

The batching configuration struct now uses a struct-level #[serde(default)] with an explicit impl Default, rather than per-field #[serde(default)] annotations. This aligns with the project's YAML design guidance, which requires that the serde deserialization path and the Default implementation use the same mechanism.

As a result of this change:

  • The mode field now has an explicit default of batch_http_link (the only supported mode), so it is no longer required in YAML configuration. Existing configurations that specify mode: batch_http_link are unaffected.
  • The subgraph batching configuration now rejects unknown fields, consistent with the rest of the router configuration.

By @BobaFetters in https://github.com/apollographql/router/pull/9315

Deprecate connectors.subgraphs configuration field (PR #9415)

The connectors.subgraphs configuration field is now deprecated in favor of connectors.sources. When connectors.subgraphs is set, the router will emit a deprecation warning at startup directing operators to rename the key. The field will be removed in a future 3.x release.

By @BobaFetters in https://github.com/apollographql/router/pull/9415

Improve Query::apply_root_selection_set performance by 5-15% (PR #9458)

Query::apply_root_selection_set now combines three separate map lookups into one, reducing work on every query plan application by 5-15%.

By @rohan-b99 in https://github.com/apollographql/router/pull/9458

📚 Documentation

Document how to log operations that exceed a candidate operation limit (PR #9294)

Adds a new section to the request limits docs showing how to use a custom telemetry event with a gt condition on the query selector to log operations that exceed a candidate max_aliases, max_depth, max_height, or max_root_fields value — without configuring limits or enabling warn_only mode. The example also captures the client name and version from the apollographql-client-name and apollographql-client-version headers so you can see which clients are sending the offending operations. The warn_only section now cross-references this approach.

By @smyrick in https://github.com/apollographql/router/pull/9294

Clarify authorization directive behavior on federated root operation types (PR #9213)

The authorization docs now explain what happens when you apply @authenticated, @requiresScopes, or @policy directly to a root operation type (Query, Mutation, or Subscription) in a subgraph. Because root operation types are shared merged types in a federated graph, the directive composes into the supergraph root type and applies to every field on that type, including fields contributed by other subgraphs. To scope authorization reliably, apply the directive to each field rather than to the root type.

By @andywgarcia in https://github.com/apollographql/router/pull/9213

🐛 Fixes

Recover the Redis-backed caches after cluster events and honor required_to_start: true on startup

The router's Redis-backed caches (query planner, entity cache, APQ, response cache) could silently stall after a network event involving Redis replicas or the full cluster — accumulating queued commands, command timeouts, latency, and memory pressure until the router was redeployed. The router now detects when the underlying Redis client has given up reconnecting, drains the connection pool, and rebuilds it on the next request. In deployments where the broadcast cluster topology contains nodes that aren't routeable from the router's network position (for example, internal IPs reserved for replica promotion), a new replica filter screens those nodes out before they enter the routing table.

The required_to_start: true flag — available on each cache under supergraph.query_planning.cache.redis, apq.router.cache.redis, preview_entity_cache.subgraph.all.redis, and experimental_response_cache.subgraph.all.redis — now actually fails the router's startup fast when Redis is unreachable, instead of hanging indefinitely or silently returning success under broadcast overflow.

The router also now supports required_to_start: false, allowing the router to start when Redis is unavailable at boot and to begin caching once Redis becomes reachable.

For more technical internal details, see PR #9023 and PR #9418. For more details on configuring the router's Redis-backed caches, see Response Cache Customization and the related caching docs.

By @aaronArinder in https://github.com/apollographql/router/pull/9023 and https://github.com/apollographql/router/pull/9418

Minor Changes
  • #13132 f3ce805 Thanks @phryneas! - Introduce "classic" and "modern" method and hook signatures.

    Apollo Client 4.2 introduces two signature styles for methods and hooks. All signatures previously present are now "classic" signatures, and a new set of "modern" signatures are added alongside them.

    Classic signatures are the default and are identical to the signatures before Apollo Client 4.2, preserving backward compatibility. Classic signatures still work with manually specified TypeScript generics (e.g., useSuspenseQuery<MyData>(...)). However, manually specifying generics has been discouraged for a long time—instead, we recommend using TypedDocumentNode to automatically infer types, which provides more accurate results without any manual annotations.

    Modern signatures automatically incorporate your declared defaultOptions into return types, providing more accurate types. Modern signatures infer types from the document node and do not support manually passing generic type arguments; TypeScript will produce a type error if you attempt to do so.

    Methods and hooks automatically switch to modern signatures the moment any non-optional property is declared in DeclareDefaultOptions. The switch happens across all methods and hooks globally:

    // apollo.d.ts
    import "@apollo/client";
    declare module "@apollo/client" {
      namespace ApolloClient {
        namespace DeclareDefaultOptions {
          interface WatchQuery {
            errorPolicy: "all"; // non-optional → modern signatures activated automatically
          }
        }
      }
    }

    Users can also manually switch to modern signatures without declaring any defaultOptions, for example when wanting accurate type inference without relying on global defaultOptions:

    // apollo.d.ts
    import "@apollo/client";
    declare module "@apollo/client" {
      export interface TypeOverrides {
        signatureStyle: "modern";
      }
    }

    Users can do a global DeclareDefaultOptions type augmentation and then manually switch back to "classic" for migration purposes:

    // apollo.d.ts
    import "@apollo/client";
    declare module "@apollo/client" {
      export interface TypeOverrides {
        signatureStyle: "classic";
      }
    }

    Note that this is not recommended for long-term use. When combined with DeclareDefaultOptions, switching back to classic results in the same incorrect types as before Apollo Client 4.2—methods and hooks will not reflect the defaultOptions you've declared.

  • #13130 dd12231 Thanks @jerelmiller! - Improve the accuracy of client.query return type to better detect the current errorPolicy. The data property is no longer nullable when the errorPolicy is none. This makes it possible to remove the undefined checks or optional chaining in most cases.

  • #13210 1f9a428 Thanks @jerelmiller! - Add support for automatic event-based refetching, such as window focus.

    The RefetchEventManager class handles automatic refetches in response to events. Apollo Client provides built-in sources for window focus and network reconnect as windowFocusSource and onlineSource.

    Event refetching is fully opt-in. Create and pass a RefetchEventManager instance to the ApolloClient constructor to activate the event listeners.

    import {
      ApolloClient,
      InMemoryCache,
      RefetchEventManager,
      windowFocusSource,
      onlineSource,
    } from "@apollo/client";
    
    const client = new ApolloClient({
      link,
      cache: new InMemoryCache(),
      refetchEventManager: new RefetchEventManager({
        sources: {
          // Refetch when window is focused
          windowFocus: windowFocusSource,
    
          // Refetch when the user comes back online
          online: onlineSource,
        },
      }),
    });

    By default, all active queries refetch when the events fire. Queries can opt out per-event or disable all event refetches:

    // Skip refetch on window focus for this query, but keep `online`
    useQuery(QUERY, {
      refetchOn: { windowFocus: false },
    });
    
    // Disable all event-driven refetches for this query
    useQuery(OTHER_QUERY, {
      refetchOn: false,
    });
    
    // Enable every event for this query, regardless of defaultOptions
    useQuery(LIVE_DASHBOARD, {
      refetchOn: true,
    });
    
    // Dynamically enable or disable a refetch when the event fires
    useQuery(LIVE_DASHBOARD, {
      refetchOn: ({ source, payload }) => {
        if (source === "windowFocus") {
          // payload is the data associated with the event
          return someCondition(payload);
        }
    
        return true;
      },
    });
    
    // Dynamically enable or disable a refetch for a specific event
    useQuery(LIVE_DASHBOARD, {
      refetchOn: {
        windowFocus: ({ payload }) => {
          // payload is the data associated with the event
          return someCondition(payload);
        },
      },
    });

    To enable per-query opt-in rather than opt-out, set defaultOptions.watchQuery.refetchOn to false and enable it per-query instead.

    const client = new ApolloClient({
      link,
      cache,
      refetchEventManager: new RefetchEventManager({
        sources: { windowFocus: windowFocusSource },
      }),
      defaultOptions: {
        watchQuery: { refetchOn: false },
      },
    });
    
    // Only this query refetches on window focus
    useQuery(DASHBOARD_QUERY, { refetchOn: { windowFocus: true } });

    When defaultOptions.watchQuery.refetchOn and per-query refetchOn options are provided, the objects are merged together.

    Custom events

    You can also add your own custom events that trigger refetches. Register your event name and payload type using TypeScript module augmentation, then provide a source function that returns an Observable. The source's emitted value becomes the event's payload.

    import { Observable } from "@apollo/client";
    import { filter } from "rxjs";
    import { AppState, AppStateStatus, Platform } from "react-native";
    
    declare module "@apollo/client" {
      interface RefetchEvents {
        reactNativeAppStatus: AppStateStatus;
      }
    }
    
    const refetchEventManager = new RefetchEventManager({
      sources: {
        reactNativeAppStatus: () => {
          return new Observable((observer) => {
            const subscription = AppState.addEventListener("change", (status) => {
              observer.next(status);
            });
            return () => subscription.remove();
          }).pipe(
            filter((status) => Platform.OS !== "web" && status === "active")
          );
        },
      },
    });
    
    // Disable per-query by setting the event to false
    useQuery(QUERY, { refetchOn: { reactNativeAppStatus: false } });
    Manually trigger an event refetch

    Refetches can be triggered imperatively by calling emit with the event name and its payload (if any).

    refetchEventManager.emit("reactNativeAppStatus", "active");
    Sourceless events

    A source that has no automatic detection logic but still wants imperative emit support can be declared as true. Type the event as void to omit the payload argument.

    declare module "@apollo/client" {
      interface RefetchEvents {
        userTriggered: void;
      }
    }
    
    const refetchEventManager = new RefetchEventManager({
      sources: { userTriggered: true },
    });
    
    refetchEventManager.emit("userTriggered");

    Note: Calling emit on an event without a registered source will log a warning and result in a no-op.

    Custom handlers

    When an event fires, the default handler calls client.refetchQueries({ include: "active" }) filtered by each query's refetchOn setting. You can override the handler for an event to add your own custom filtering. For example, to refetch all queries, including standby queries, define a handler for the event:

    const refetchEventManager = new RefetchEventManager({
      // ...
      handlers: {
        userTriggered: ({ client, source, payload, matchesRefetchOn }) => {
          return client.refetchQueries({
            include: "all",
            onQueryUpdated: (observableQuery) => {
              return matchesRefetchOn(observableQuery);
            },
          });
        },
      },
    });

    Handlers must return either a RefetchQueriesResult or void. Returning void skips refetching for the event.

  • #13232 f1b541f Thanks @jerelmiller! - Version bump to rc.

  • #13206 08fccab Thanks @jerelmiller! - Extend the defaultOptions type-safety work to client.mutate and useMutation.

    The errorPolicy option now flows through to the result types for mutations in the same way it already does for queries:

    • ApolloClient.MutateResult<TData, TErrorPolicy> maps errorPolicy to the concrete shape of data and error:
      • "none"{ data: TData; error?: never }
      • "all"{ data: TData | undefined; error?: ErrorLike }
      • "ignore"{ data: TData | undefined; error?: never }
    • client.mutate and useMutation pick up the declared defaultOptions.mutate.errorPolicy and the explicit errorPolicy on each call to narrow return types accordingly.
    • useMutation.Result.error is narrowed to undefined when errorPolicy is "ignore", since client.mutate never resolves with an error in that case.

    DeclareDefaultOptions.Mutate already accepted errorPolicy; the new behavior is that once you declare it, hook and method return types reflect it:

    // apollo.d.ts
    import "@apollo/client";
    
    declare module "@apollo/client" {
      namespace ApolloClient {
        namespace DeclareDefaultOptions {
          interface Mutate {
            errorPolicy: "all";
          }
        }
      }
    }
    const result = await client.mutate({ mutation: MUTATION });
    result.data;
    //     ^? TData | undefined
    result.error;
    //     ^? ErrorLike | undefined

    Setting errorPolicy on an individual call overrides the default for that call's return type.

  • #13222 b93c172 Thanks @jerelmiller! - Extend the defaultOptions type-safety work to preloadQuery (returned from createQueryPreloader). Defaults declared in DeclareDefaultOptions.WatchQuery now work with preloadQuery to ensure the PreloadedQueryRef's data states are correctly set.

    // apollo.d.ts
    import "@apollo/client";
    
    declare module "@apollo/client" {
      namespace ApolloClient {
        namespace DeclareDefaultOptions {
          interface WatchQuery {
            errorPolicy: "all";
          }
        }
      }
    }
    const preloadQuery = createQueryPreloader(client);
    const queryRef = preloadQuery(QUERY);
    //    ^? PreloadedQueryRef<TData, TVariables, "complete" | "streaming" | "empty">
  • #13132 f3ce805 Thanks @phryneas! - Synchronize method and hook return types with defaultOptions.

    Prior to this change, the following code snippet would always apply:

    declare const MY_QUERY: TypedDocumentNode<TData, TVariables>;
    const result1 = useSuspenseQuery(MY_QUERY);
    result1.data;
    //      ^? TData
    const result2 = useSuspenseQuery(MY_QUERY, { errorPolicy: "all" });
    result2.data;
    //      ^? TData | undefined

    While these types are generally correct, if you were to set errorPolicy: 'all' as a default option, the type of result.data for the first query would remain TData instead of changing to TData | undefined to match the runtime behavior.

    We are now enforcing that certain defaultOptions types need to be registered globally. This means that if you want to use errorPolicy: 'all' as a default option for a query, you will need to register its type like this:

    // apollo.d.ts
    import "@apollo/client";
    
    declare module "@apollo/client" {
      namespace ApolloClient {
        namespace DeclareDefaultOptions {
          interface WatchQuery {
            // possible global-registered values:
            // * `errorPolicy`
            // * `returnPartialData`
            errorPolicy: "all";
          }
          interface Query {
            // possible global-registered values:
            // * `errorPolicy`
          }
          interface Mutate {
            // possible global-registered values:
            // * `errorPolicy`
          }
        }
      }
    }

    Once this type declaration is in place, the type of result.data in the above example will correctly be changed to TData | undefined, reflecting the possibility that if an error occurs, data might be undefined. Manually specifying useSuspenseQuery(MY_QUERY, { errorPolicy: "none" }); changes result.data to TData to reflect the local override.

    This change means that you will need to declare your default options types in order to use defaultOptions with ApolloClient, otherwise you will see a TypeScript error.

    Without the type declaration, the following (previously valid) code will now error:

    new ApolloClient({
      link: ApolloLink.empty(),
      cache: new InMemoryCache(),
      defaultOptions: {
        watchQuery: {
          // results in a type error:
          // Type '"all"' is not assignable to type '"A default option for watchQuery.errorPolicy must be declared in ApolloClient.DeclareDefaultOptions before usage. See https://www.apollographql.com/docs/react/data/typescript#declaring-default-options-for-type-safety."'.
          errorPolicy: "all",
        },
      },
    });

    If you are creating multiple instances of Apollo Client with conflicting default options and you cannot register a single defaultOptions value as a result, you can relax the constraints by declaring those options as union types covering all values used by all clients. The properties can be required (to enforce them in defaultOptions) or optional (if some constructor calls won't pass a value):

    // apollo.d.ts
    import "@apollo/client";
    
    declare module "@apollo/client" {
      export namespace ApolloClient {
        export namespace DeclareDefaultOptions {
          interface WatchQuery {
            errorPolicy?: "none" | "all" | "ignore";
            returnPartialData?: boolean;
          }
          interface Query {
            errorPolicy?: "none" | "all" | "ignore";
          }
          interface Mutate {
            errorPolicy?: "none" | "all" | "ignore";
          }
        }
      }
    }

    With this declaration, the ApolloClient constructor accepts any of those values in defaultOptions. The tradeoff is that hook and method return types become more generic. For example, calling useSuspenseQuery without an explicit errorPolicy will return a result typed as if all error policies are possible, since TypeScript can't know which specific value your instance uses at runtime.

    Note that making a property optional (errorPolicy?:) is equivalent to adding the TypeScript default value ("none") to the union. So errorPolicy?: "all" | "ignore" has the same effect on return types as errorPolicy: "none" | "all" | "ignore", because TypeScript assumes the option could also be absent (i.e., "none").

    You can also use a partial union that only lists the values you actually use. For example, if you only ever use "all" or "ignore", declare errorPolicy: "all" | "ignore" (required) to keep the union narrow and avoid unused values broadening your signatures unnecessarily.

Patch Changes
  • #13217 790f987 Thanks @jerelmiller! - Fix the deprecation for the classic signatures for function overloads that rely on type inference from a TypedDocumentNode. The deprecation now only applies to classic signatures that provide explicit type arguments to encourage the use of TypedDocumentNode.

  • #13166 0537d97 Thanks @jerelmiller! - Release changes in 4.1.5 and 4.1.6.

  • #13215 54c9eb7 Thanks @jerelmiller! - Ensure the options object for the useQuery, useSuspenseQuery, and useBackgroundQuery hooks provide proper IntelliSense suggestions.

  • #13229 9a7f65a Thanks @jerelmiller! - Fix refetchOn merging when defaultOptions.watchQuery.refetchOn is set to a non-object value (false, true, or a function) and the per-query refetchOn is an object. Previously the per-query object completely replaced the default so unspecified events fell back to "enabled" regardless of the default.

    The defaultOptions value now applies to any event the per-query object does not explicitly configure:

    • false - unspecified events stay disabled
    • true - unspecified events refetch
    • Callback function - the function is called for unspecified events to determine whether to refetch
    const client = new ApolloClient({
      // ...
      defaultOptions: {
        watchQuery: {
          refetchOn: false,
        },
      },
    });
    
    // Only `windowFocus` refetches. Other events stay disabled per the default.
    useQuery(QUERY, { refetchOn: { windowFocus: true } });
  • #13230 b25b659 Thanks @jerelmiller! - Add the ability to override the default event handler on RefetchEventManager. The default handler runs when no per-source handler is configured for an event. Provide a custom handler via the defaultHandler constructor option or the setDefaultEventHandler instance method.

    new RefetchEventManager({
      defaultHandler: ({ client, matchesRefetchOn }) => {
        return client.refetchQueries({
          include: "all",
          onQueryUpdated: matchesRefetchOn,
        });
      },
    });

🚀 Features

New rover client extract command pulls GraphQL operations out of client code. Includes the core extraction logic, CLI wiring, and integration tests.

New rover client check subcommand for validating client operations against a schema.

Runs graph check and graph publish as a single unit when --check is passed.

Allows publishing a placeholder schema without needing to provide your own schema file. This is useful for setting up your graph structure before your actual schemas are ready. The placeholder schema is type Query { helloWorld: String } with a routing URL of https://example.com/.

rover init --mcp now honors --insecure-accept-invalid-certs when fetching templates.

🐛 Fixes

Restores no_proxy / NO_PROXY handling that was previously provided by axios before its removal in 0.38.0.

Skips the rover version-update check when running inside a container so Docker users don't see spurious update prompts.

🛠 Maintenance

New companion actions under apollographql-gh-actions/* for installing rover and wrapping common subgraph and persisted-queries subcommands.


This release was automatically created by GitHub Actions.

If you would like to verify that the binary you have downloaded was built from the source code in this repository, you can compute a checksum of the zipped tarball and compare it to the checksums that are included as release artifacts.

Binaries built for MacOS are signed, notarized, and automatically verified with Gatekeeper.

🐛 Fixes

Avoid spurious "meter provider after shutdown" error during router shutdown (PR #9248)

The router no longer emits the spurious cannot use meter provider after shutdown error during shutdown. The metrics aggregation layer now returns a noop instrument in that path instead of panicking.

@rohan-b99 in https://github.com/apollographql/router/pull/9248

Use lazy idle eviction in connection pool to avoid inter-request TCP closes (PR #9308)

When pool_idle_timeout was introduced in v2.13.0, the router unconditionally enabled a background timer that proactively closed idle connections exceeding the timeout. In some network environments, the TCP close sent by this background task raced with a new connection attempt and caused significant latency spikes on the next request.

The router now uses lazy eviction: connections are only closed at checkout time, when a request finds a pooled connection that has exceeded pool_idle_timeout. No TCP closes are sent between requests. This matches router behavior before v2.13.0.

@carodewig in https://github.com/apollographql/router/pull/9308

Include URL and failure category in JWKS fetch error logs (PR #9258)

When the JWKS server is unreachable, the router now logs a specific, actionable message including the URL and the failure category (timed out, connection failed, or generic failure) — replacing the previous vague "could not get url" message.

@carodewig in https://github.com/apollographql/router/pull/9258

Pick up hickory 0.26.1 to close two upstream DNS DoS advisories (RUSTSEC-2026-0119, RUSTSEC-2026-0120)

The router's DNS resolver (via hickory-resolver) inherits two upstream advisories in hickory-proto / hickory-net 0.26.0. Both are fixed in 0.26.1, which is now pinned in Cargo.lock.

Source-built consumers were already insulated by caret-semver dependency declarations; this change picks up the fix in Apollo's pre-built binaries and Docker images.

@carodewig in https://github.com/apollographql/router/pull/9321

Return a single JSON response for unsupported defer-with-batch queries (PR #9311)

Batched queries that use @defer are not supported by the router. Previously these requests produced a malformed multipart response; they now return a single JSON response with errors that explicitly indicates the lack of support.

@rohan-b99 in https://github.com/apollographql/router/pull/9311

Apply http_max_request_bytes only to the operations field, not file streams (PR #9226, PR #9327)

Previously, limits.http_max_request_bytes (default 2 MB) was applied to the entire multipart body of file upload requests, causing large file uploads to be rejected even when preview_file_uploads.protocols.multipart.limits.max_file_size was configured to allow them.

The limit now applies only to the GraphQL operations field (the query and variables). File data is bounded separately by max_file_size, enforced by the multer parser.

@carodewig in https://github.com/apollographql/router/pull/9226 and https://github.com/apollographql/router/pull/9327

🛠 Maintenance

Instrument experimental config features with OTLP gauges (PR #9330)

Adds apollo.router.config.experimental_* OTLP gauge metrics for all customer-facing experimental config flags, using the existing populate_config_instrument! pattern in configuration/metrics.rs. This enables Apollo to track adoption of experimental features so we can inform decisions about which to promote or remove in future releases.

Features now instrumented:

  • experimental_chaos
  • experimental_type_conditioned_fetching
  • experimental_hoist_orphan_errors
  • experimental_log_on_broken_pipe
  • experimental_plans_limit
  • experimental_paths_limit
  • experimental_reuse_query_plans
  • experimental_cooperative_cancellation
  • experimental_prewarm_query_plan_cache
  • experimental_local_field_metrics
  • experimental_response_trace_id
  • experimental_otlp_endpoint
  • experimental_otlp_tracing_protocol
  • experimental_otlp_metrics_protocol
  • experimental_http2
  • experimental_http2_keep_alive_interval
  • experimental_http2_keep_alive_timeout
  • experimental_mock_subgraphs
  • experimental.expose_query_plan (recorded as apollo.router.config.experimental_expose_query_plan)

The mandatory experimental_diagnostics plugin is intentionally excluded because it is loaded on every router and would always report adoption as 100%.

@aaronArinder in https://github.com/apollographql/router/pull/9330

Avoid unnecessary clones on subgraph requests (PR #9266)

The router now avoids some unnecessary memory allocations when making subgraph requests, particularly on the APQ (Automatic Persisted Queries) path.

@carodewig in https://github.com/apollographql/router/pull/9266

Improve query plan cache throughput with an in-memory fast path (PR #9279)

Every query plan cache lookup — including cache hits — previously acquired the wait_map mutex before checking whether the value was in memory. On a warm cache this was pure overhead: the mutex was locked twice, a broadcast::Sender was allocated, and a cleanup task was spawned, all to be immediately discarded.

A fast path now checks the in-memory cache before acquiring the mutex. On a hit the value is returned immediately; the wait_map path is only entered on a miss, which is the only case where deduplication is needed.

By @theJC in https://github.com/apollographql/router/pull/9279

🚀 Features

  • Add rover client extract command - @dotdat PR #3223, #3224, #3225

    New rover client extract command pulls GraphQL operations out of client code. Includes the core extraction logic, CLI wiring, and integration tests.

  • Add rover client check command - @dotdat PR #3120

    New rover client check subcommand for validating client operations against a schema.

  • Add rover schema search - @dotdat PR #3262

    New subcommand under rover schema for searching schema contents.

  • Add --check flag to rover graph publish - @joshuaoshields95 PR #3203

    Runs graph check and graph publish as a single unit when --check is passed.

  • Add --use-example-schema flag to subgraph publish - @samaanghani PR #3218

    Allows publishing a placeholder schema without needing to provide your own schema file. This is useful for setting up your graph structure before your actual schemas are ready. The placeholder schema is type Query { helloWorld: String } with a routing URL of https://example.com.

  • Respect --insecure-accept-invalid-certs in rover init --mcp - @dotdat PR #3234

    rover init --mcp now honors --insecure-accept-invalid-certs when fetching templates.

🐛 Fixes

  • Port axios no-proxy behavior - @SharkBaitDLS PR #3270

    Restores no_proxy / NO_PROXY handling that was previously provided by axios before its removal in 0.38.0.

  • Fix path and formatting issues in client check/client extract - @dotdat PR #3285

  • Use --root-dir in client check fixture tests for cross-platform compatibility - @samaanghani PR #3219

  • Don't run the automated update check in Docker - @SharkBaitDLS PR #3245

    Skips the rover version-update check when running inside a container so Docker users don't see spurious update prompts.

  • Workaround NPM 11 installation bug - @SharkBaitDLS PR #3230

🛠 Maintenance

  • Add canonical GitHub Actions for install / subgraph / persisted queries - @SharkBaitDLS PR #3264, #3269, #3278, #3279, #3280

    New companion actions under apollographql-gh-actions/* for installing rover and wrapping common subgraph and persisted-queries subcommands.

  • Bump apollo-language-server to 0.8.0 - PR #3251


This release was automatically created by GitHub Actions.

If you would like to verify that the binary you have downloaded was built from the source code in this repository, you can compute a checksum of the zipped tarball and compare it to the checksums that are included as release artifacts.

Binaries built for MacOS are signed, notarized, and automatically verified with Gatekeeper.

We're excited to announce Apollo Kotlin 5 is now available.

Apollo Kotlin 3 was a full rewrite of Apollo Android for Kotlin multiplatform. Apollo Kotlin 4 reworked error handling, introduced the Apollo IDE plugin and semantic nullability.

Apollo Kotlin 5 is GraphQL Golden Path ready, comes with a new normalized cache, a rework of the Gradle plugin, new compiler plugins APIs, agent skills, linuxX64 and watchosDeviceArm64 targets and more.

If you're currently using Apollo Kotlin 4, you should feel right at home with Apollo Kotlin 5. Most APIs are untouched. For the others, read the migration guide for details.

For a comprehensive list of all changes, please review the full changelog or read on for a brief summary of the key updates.

GraphQL Golden Path

Apollo Kotlin aims to support the latest version of the GraphQL draft specification. Making a change to the specification draft is a long and near-irreversible process. Yet experimentation matters: it gives the community confidence that proposed changes are sound before they ship.

For this reason, Apollo Kotlin 5 supports a number of experimental GraphQL RFCs:

Make sure to join an upcoming working group and share feedback if you're using any of these.

Normalized cache

Apollo Kotlin 5 comes with a new, separately versioned, normalized cache that supports:

  • TTL
  • Garbage collection
  • Pagination
  • Binary format
  • Partial results

For more details, read the dedicated blog post.

Modernization

The Gradle plugin now uses Gratatouille classloader isolation, instead of GR8 relocation previously. This makes the plugin more robust and easier to debug.

Apollo Kotlin 5 uses KGP 2.3, with 2.1 compatibility for JVM and Android consumers. Native and JS consumers must compile with KGP 2.3+.

If you are using Apollo Kotlin with AI agents, the Apollo Kotlin agent skill is now available. Agents can use it to discover the Apollo Kotlin best practices.

Migration path

Previous DeprecationLevel.WARNING symbols are now DeprecationLevel.ERROR. Previous DeprecationLevel.ERROR symbols are removed.

Most of the runtime APIs as well as the package name are unchanged. If your build has no Apollo deprecation warnings on v4, the upgrade should require minimal changes.

The main breaking changes are in experimental Data Builders and Apollo Compiler Plugins.

See the v5 migration guide for a complete upgrade walkthrough.

Update today

Apollo Kotlin 5 is now available on Maven Central:

plugins {
  id("com.apollographql.apollo").version("5.0.0")
}

dependencies {
  implementation("com.apollographql.apollo:apollo-runtime:5.0.0")
}

Any feedback? Let us know what you think! The team is looking forward to seeing what you build!

Last Checked
1h ago
Tracking since Jun 11, 2020