Releases: apollographql/router
v2.11.0
🚀 Features
Support client awareness metadata via HTTP headers (PR #8503)
Clients can now send library name and version metadata for client awareness and enhanced client awareness using HTTP headers. This provides a consistent transport mechanism instead of splitting values between headers and request.extensions.
By @calvincestari in #8503
Reload OCI artifacts when a tag reference changes (PR #8805)
You can now configure tag-based OCI references in the router. When you use a tag reference such as artifacts.apollographql.com/my-org/my-graph:prod, the router polls and reloads when that tag points to a new artifact.
This also applies to automatically generated variant tags and custom tags.
By @graytonio in #8805
Add memory limit option for cooperative cancellation (PR #8808)
The router now supports a memory_limit option on experimental_cooperative_cancellation to cap memory allocations during query planning. When the memory limit is exceeded, the router:
- In
enforcemode, cancels query planning and returns an error to the client. - In
measuremode, records the cancellation outcome in metrics and allows query planning to complete.
The memory limit works alongside the existing timeout option. Whichever limit is reached first triggers cancellation.
This feature is only available on Unix platforms when the global-allocator feature is enabled and dhat-heap is not enabled.
Example configuration:
supergraph:
query_planning:
experimental_cooperative_cancellation:
enabled: true
mode: enforce # or "measure" to only record metrics
memory_limit: 50mb # Supports formats like "50mb", "1gb", "1024kb", etc.
timeout: 5s # Optional: can be combined with memory_limitBy @rohan-b99 in #8808
Add memory tracking metrics for requests (PR #8717)
The router now emits two histogram metrics to track memory allocation activity during request processing:
apollo.router.request.memory: Memory activity across the full request lifecycle (including parsing, validation, query planning, and plugins)apollo.router.query_planner.memory: Memory activity for query planning work in the compute job thread pool
Each metric includes:
allocation.type:allocated,deallocated,zeroed, orreallocatedcontext: The tracking context name (for example,router.requestorquery_planning)
This feature is only available on Unix platforms when the global-allocator feature is enabled and dhat-heap is not enabled.
By @rohan-b99 in #8717
🐛 Fixes
Support nullable @key fields in response caching (PR #8767)
Response caching can now use nullable @key fields. Previously, the response caching feature rejected nullable @key fields, which prevented caching in schemas that use them.
When you cache data keyed by nullable fields, keep your cache keys simple and avoid ambiguous null values.
By @aaronArinder in #8767
Return 429 instead of 503 when enforcing a rate limit (PR #8765)
In v2.0.0, the router changed the rate-limiting error from 429 (TOO_MANY_REQUESTS) to 503 (SERVICE_UNAVAILABLE). This change restores 429 to align with the router error documentation.
By @carodewig in #8765
Add status code and error type attributes to http_request spans (PR #8775)
The router now always adds the http.response.status_code attribute to http_request spans (for example, for router -> subgraph requests). The router also conditionally adds error.type for non-success status codes.
By @rohan-b99 in #8775
Report response cache invalidation failures as errors (PR #8813)
The router now returns an error when response cache invalidation fails. Previously, an invalidation attempt could fail without being surfaced as an error.
After you upgrade, you might see an increase in the apollo.router.operations.response_cache.invalidation.error metric.
Reuse response cache Redis connections for identical subgraph configuration (PR #8764)
The response cache now reuses Redis connection pools when subgraph-level configuration resolves to the same Redis configuration as the global all setting. Previously, the router could create redundant Redis connections even when the effective configuration was identical.
Impact: If you configure response caching at both the global and subgraph levels, you should see fewer Redis connections and lower connection overhead.
Prevent TLS connections from hanging when a handshake stalls (PR #8779)
The router listener loop no longer blocks while waiting for a TLS handshake to complete. Use server.http.tls_handshake_timeout to control how long the router waits before terminating a connection (default: 10s).
By @rohan-b99 in #8779
Emit cardinality overflow metrics for more OpenTelemetry error formats (PR #8740)
The router now emits the apollo.router.telemetry.metrics.cardinality_overflow metric for additional OpenTelemetry cardinality overflow error formats.
Propagate trace context on WebSocket upgrade requests (PR #8739)
The router now injects trace propagation headers into the initial HTTP upgrade request when it opens WebSocket connections to subgraphs. This preserves distributed trace continuity between the router and subgraph services.
Trace propagation happens during the HTTP handshake only. After the WebSocket connection is established, headers cannot be added to individual messages.
Stop query planning compute jobs when the parent task is canceled (PR #8741)
Query planning compute jobs now stop when cooperative cancellation cancels the parent task.
By @rohan-b99 in #8741
Reject invalidation requests with unknown fields (PR #8752)
The response cache invalidation endpoint now rejects request payloads that include unknown fields. When unknown fields are present, the router returns HTTP 400 (Bad Request).
Restore plugin access to SubscriptionTaskParams in execution::Request builders (PR #8771)
Plugins and other external crates can use SubscriptionTaskParams with execution::Request builders again. This restores compatibility for plugin unit tests that construct subscription requests.
By @aaronArinder in #8771
Support JWT tokens with multiple audiences (PR #8780)
When issuers or audiences is included in the router's JWK configuration, the router will check each request's JWT for iss or aud and reject requests with mismatches.
Expected behavior:
- If present, the
issclaim must be specified as a string.- ✅ The JWK's
issuersis empty. - ✅ The
issis a string and is present in the JWK'sissuers. - ✅ The
issis null. - ❌ The
issis a string but is not present in the JWK'sissuers. - ❌ The
issis not a string or null.
- ✅ The JWK's
- If present, the
audclaim can be specified as either a string or an array of strings.- ✅ The JWK's
audiencesis empty. - ✅ The
audis a string and is present in the JWK'saudiences. - ✅ The
audis an array of strings and at least one of those strings is present in the JWK'saudiences. - ❌ The
audis not a string or array of strings (i.e., null).
- ✅ The JWK's
Behavior prior to this change:
- If the
isswas not null or a string, it was permitted (regardless of its value). - If the
audwas an array, it was rejected (regardless of its value).
By @carodewig in #8780
Enforce feature restrictions for warning-state licenses (PR #8768)
The router now enforces license restrictions even when a license is in a warning state. Previously, warning-state licenses could bypass enforcement for restricted features.
If your deployment uses restricted features, the router returns an error instead of continuing to run.
By @aaronArinder in #8768
🛠 Maintenance
Warn at startup when OTEL_EXPORTER_OTLP_ENDPOINT is set (PR #8729)
The ...
v2.11.0-rc.0
2.11.0-rc.0
v2.11.0-abstract.1
2.11.0-abstract.1
v2.10.0 (LTS)
Long-Term Support
This release is marked for LTS under the v2026.1 LTS Policy for the GraphOS Runtime. It will be supported until September 30, 2026 with patch updates.
🚀 Features
Response caching is now Generally Available 🎉 (PR #8678)
Response caching is now Generally Available (GA) and ready for production use!
Response caching enables the router to cache subgraph query responses using Redis, improving query latency and reducing load on your underlying services. Unlike traditional HTTP caching solutions, response caching provides GraphQL-aware caching at the entity and root field level, making cached data reusable across different users and queries.
For complete documentation, configuration options, and quickstart guide, see the response caching documentation.
Key benefits
- Improved performance: Cache origin responses and reuse them across queries to reduce latency
- Reduced subgraph load: Minimize redundant requests to your subgraphs by serving cached data
- Entity-level caching: Cache individual entity representations independently, enabling fine-grained control over data freshness
- Flexible cache control: Set different TTLs for different types of data based on
@cacheControldirectives orCache-Controlresponse headers - Privacy-aware: Share cached data across users while maintaining privacy for personalized data
- Active cache invalidation: Tag cached data with
@cacheTagand invalidate specific cache entries via HTTP endpoint when data changes
What's cached
The router caches two kinds of data:
- Root query fields: Cached as complete units (the entire response for these root fields)
- Entity representations: Cached independently—each origin's contribution to an entity is cached separately and can be reused across different queries
Additional features
- Cache debugger: See exactly what's being cached during development
- Redis cluster support: Scale your cache with Redis cluster deployments and read replicas
- Comprehensive metrics: Monitor cache performance with detailed Redis-specific metrics
Support Redis read replicas (PR #8405)
Read-only queries are now sent to replica nodes when using clustered Redis. Previously, all commands were sent to the primary nodes.
This change applies to all Redis caches, including the query plan cache and the response cache.
By @carodewig in #8405
Enable HTTP/2 header size limits for TCP and UDS (PR #8673)
The router's HTTP/2 header size limit configuration option now applies to requests using TCP and UDS (Unix domain sockets). Previously, this setting only worked for TLS connections.
By @aaronArinder in #8673
🐛 Fixes
Enable invalidation endpoint when any subgraph has invalidation enabled (PR #8680)
Previously, the response cache invalidation endpoint was only enabled when global invalidation was enabled via response_cache.subgraph.all.invalidation.enabled. If you enabled invalidation for only specific subgraphs without enabling it globally, the invalidation endpoint wouldn't start, preventing cache invalidation requests from being processed.
The invalidation endpoint now starts if either:
- Global invalidation is enabled (
response_cache.subgraph.all.invalidation.enabled: true), OR - Any individual subgraph has invalidation enabled
This enables more flexible configuration where you can enable invalidation selectively for specific subgraphs:
response_cache:
enabled: true
invalidation:
listen: 127.0.0.1:4000
path: /invalidation
subgraph:
all:
enabled: true
# Global invalidation not enabled
subgraphs:
products:
invalidation:
enabled: true # Endpoint now starts
shared_key: Require Redis configuration only when response caching is enabled (PR #8684)
Previously, the router attempted to connect to Redis for response caching regardless of whether response caching was enabled or disabled. This caused unnecessary connection attempts and configuration errors even when the feature was explicitly disabled.
The router now ignores Redis configuration if response caching is disabled. If response caching is configured to be enabled, Redis configuration is required, and missing Redis configuration raises an error on startup:
Error: you must have a redis configured either for all subgraphs or for subgraph "products"
Prevent deleted coprocessor context keys from reappearing in later stages (PR #8679)
Coprocessor context keys deleted in a previous stage no longer reappear in later stages.
By @rohan-b99 in #8679
Customize response caching behavior at the subgraph level (PR #8652)
You can now customize cached responses using Rhai or coprocessors. You can also set a different private_id based on subgraph request headers.
Example Rhai script customizing private_id:
fn subgraph_service(service, subgraph) {
service.map_request(|request| {
if "private_id" in request.headers {
request.context["private_id"] = request.headers["private_id"];
}
});
}
Prevent glibc mismatch in DIY Docker images (Issue #8450)
The DIY Dockerfile now pins the Rust builder to the Bookworm variant (for example, rust:1.91.1-slim-bookworm) so the builder and runtime share the same Debian base. This prevents the image from failing at startup with /lib/x86_64-linux-gnu/libc.so.6: version 'GLIBC_2.39' not found.
This resolves a regression introduced when the rust:1.90.0 bump used a generic Rust image without specifying a Debian variant. The upstream Rust image default advanced to a newer variant with glibc 2.39, although the DIY runtime remained on Bookworm, creating a version mismatch.
Correct response cache fetch error metric (PR #8711)
The apollo.router.operations.response_cache.fetch.error metric was out of sync with the apollo.router.cache.redis.errors metric because errors weren't being returned from the Redis client wrapper. The response caching plugin now increments the error metric as expected.
By @carodewig in #8711
Emit http.client.request.body.size metric correctly (PR #8712)
The histogram for http.client.request.body.size was using the SubgraphRequestHeader selector, looking for Content-Length before it had been set in on_request, so http.client.request.body.size wasn't recorded. The router now uses the on_response handler and stores the body size in the request context extensions.
By @rohan-b99 in #8712
Record http.server.response.body.size metric correctly (PR #8697)
Previously, the http.server.response.body.size metric wasn't recorded because the router attempted to read from the Content-Length header before it had been set. The router now uses the size_hint of the body if it's exact.
By @rohan-b99 in #8697
Treat interface objects as entities in response caching (PR #8582)
Interface objects can be entities, but response caching wasn't treating them that way. Interface objects are now respected as entities so they can be used as cache keys.
By @aaronArinder in #8582
🛠 Maintenance
Warn when Datadog propagator isn't exclusively active (PR #8677)
The router now validates propagator configuration and emits a warning log if:
- The Datadog propagator is enabled and any other propagators are enabled (except baggage)
- Datadog tracing is enabled and other propagators are enabled (except baggage)
By @rohan-b99 in #8677
v2.10.0-rc.0
2.10.0-rc.0
v2.9.0
🚀 Features
Add CORS Private Network Access support (PR #8279)
CORS configuration now supports private network access (PNA). Enable PNA for a CORS policy by specifying the private_network_access field, which supports two optional subfields: access_id and access_name.
Example configuration:
cors:
policies:
- origins: ["https://studio.apollographql.com"]
private_network_access:
access_id:
- match_origins: ["^https://(dev|staging|www)?\\.my-app\\.(com|fr|tn)$"]
private_network_access:
access_id: "01:23:45:67:89:0A"
access_name: "mega-corp device"By @TylerBloom in #8279
Configure maximum HTTP/2 header list size (PR #8636)
The router now supports configuring the maximum size for HTTP/2 header lists via the limits.http2_max_headers_list_bytes setting. This protects against excessive resource usage from clients sending large sets of HTTP/2 headers.
The default remains 16KiB. When a client sends a request with HTTP/2 headers whose total size exceeds the configured limit, the router rejects the request with a 431 error code.
Example configuration:
limits:
http2_max_headers_list_bytes: "48KiB"By @aaronArinder in #8636
Customize response cache key per subgraph via context (PR #8543)
The response cache key can now be customized per subgraph using the apollo::response_cache::key context entry. The new subgraphs field enables defining separate cache keys for individual subgraphs.
Subgraph-specific data takes precedence over data in the all field—the router doesn't merge them. To set common data when providing subgraph-specific data, add it to the subgraph-specific section.
Example payload:
{
"all": 1,
"subgraph_operation1": "key1",
"subgraph_operation2": {
"data": "key2"
},
"subgraphs": {
"my_subgraph": {
"locale": "be"
}
}
}Add telemetry selector for Cache-Control metrics (PR #8524)
The new response_cache_control selector enables telemetry metrics based on the computed Cache-Control header from subgraph responses.
Example configuration:
telemetry:
exporters:
metrics:
common:
service_name: apollo-router
views:
- name: subgraph.response.cache_control.max_age
aggregation:
histogram:
buckets:
- 10
- 100
- 1000
- 10000
- 100000
instrumentation:
instruments:
subgraph:
subgraph.response.cache_control.max_age:
value:
response_cache_control: max_age
type: histogram
unit: s
description: A histogram of the computed TTL for a subgraph response🐛 Fixes
Remove _redacted suffix from event attributes in apollo.router.state.change.total metric (Issue #8464)
Event names in the apollo.router.state.change.total metric no longer include the _redacted suffix. The metric now uses the Display trait instead of Debug for event names, changing values like updateconfiguration_redacted to updateconfiguration in APM platforms.
The custom behavior for UpdateLicense events is retained—the license state name is still appended.
By @rohan-b99 in #8464
Preserve Content-Length header for responses with known size (Issue #7941)
The router now uses the Content-Length header for GraphQL responses with known content lengths instead of transfer-encoding: chunked. Previously, the fleet_detector plugin destroyed HTTP body size hints when collecting metrics.
This extends the fix from #6538, which preserved size hints for router → subgraph requests, to also cover client → router requests and responses. Size hints now flow correctly through the entire pipeline for optimal HTTP header selection.
By @morriswchris in #7977
Correct apollo.router.operations.subscriptions.events metric counting (PR #8483)
The apollo.router.operations.subscriptions.events metric now increments correctly for each subscription event (excluding ping/pong/close messages). The counter call has been moved into the stream to trigger on each event.
This change also removes custom pong response handling before connection acknowledgment, which previously caused duplicate pongs because the WebSocket implementation already handles pings by default.
By @rohan-b99 in #8483
Unify timeout codes in response caching metrics (PR #8515)
Tokio- and Redis-based timeouts now use the same timeout code in apollo.router.operations.response_cache.*.error metrics. Previously, they were inadvertently given different code values.
By @carodewig in #8515
📃 Configuration
Remove unused TTL parameter from response cache Redis configuration (PR #8513)
The ttl parameter under redis configuration had no effect and is removed. Configure TTL at the subgraph level to control cache entry expiration:
preview_response_cache:
enabled: true
subgraph:
all:
enabled: true
ttl: 10m # ✅ Configure TTL here
redis:
urls: [ "redis://..." ]
# ❌ ttl was here previously (unused)By @carodewig in #8513
📚 Documentation
Document active subgraph requests selector (PR #8530)
The telemetry selectors documentation now correctly reflects the active_subgraph_requests attribute.
By @faisalwaseem in #8530
Add Redis cache suggestions to response cache documentation (PR #8624)
The FAQ now includes information about supported Redis versions and Redis key eviction setup.
By @carodewig in #8624
v2.9.0-rc.4
2.9.0-rc.4
v2.9.0-rc.3
2.9.0-rc.3
v2.9.0-rc.1
2.9.0-rc.1
v2.9.0-rc.0
2.9.0-rc.0