fix(subscription): handle missing DgsContext in subscription callbacks
Pull request checklist
- [x] Please read our contributor guide
- [x] Consider creating a discussion on the discussion forum first
- [x] Make sure the PR doesn't introduce backward compatibility issues
- [x] Make sure to have sufficient test cases
Pull Request type
- [x] Bugfix
- [ ] Feature
- [ ] Refactoring (no functional changes, no api changes)
- [ ] Build related changes
- [ ] Other (please describe):
Changes in this PR
This PR fixes a NullPointerException that occurs when using Apollo Federation subscription callbacks with ApolloFederatedTracingHeaderForwarder enabled.
Issue #2077
Problem
When using Apollo Router with Netflix DGS Framework for GraphQL subscriptions with HTTP callbacks, enabling ApolloFederatedTracingHeaderForwarder causes:
java.lang.NullPointerException: get(...) must not be null
at com.netflix.graphql.dgs.context.DgsContext$Companion.from(DgsContext.kt:46)
at com.netflix.graphql.dgs.context.GraphQLContextContributorInstrumentation.createState(GraphQLContextContributorInstrumentation.kt:42)
Root Cause
The issue is caused by an interceptor execution order problem:
CallbackWebGraphQLInterceptorruns atLOWEST_PRECEDENCE(designed to run LAST)GraphQLContextContributorInstrumentation.createState()is called BEFOREDgsWebFluxGraphQLInterceptor- Attempts to call
DgsContext.from(graphQLContext)before DgsContext has been added to GraphQL context
Solution
Add null-safe access to DgsContext for subscription callback scenarios:
- Add
DgsContext.fromOrNull()method - A safe accessor that returns null instead of throwing NPE when DgsContext is not present - Update
GraphQLContextContributorInstrumentation- Use null-safe access to handle cases where DgsContext may not be initialized yet
Files Changed
-
graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/context/DgsContext.kt- Add
fromOrNull()companion method with KDoc documentation
- Add
-
graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/context/GraphQLContextContributorInstrumentation.kt- Use null-safe
DgsContext.fromOrNull()instead ofDgsContext.from()
- Use null-safe
-
New test files:
graphql-dgs/src/test/kotlin/com/netflix/graphql/dgs/context/DgsContextTest.ktgraphql-dgs/src/test/kotlin/com/netflix/graphql/dgs/context/GraphQLContextContributorInstrumentationTest.kt
Backwards Compatibility
This is a backwards-compatible bug fix:
DgsContext.from()still throws NPE when context missing (existing behavior preserved)DgsContext.fromOrNull()is a new method - no impact on existing code- Context contributors must handle null
requestDatagracefully, but implementations already use nullable types
Alternatives considered
-
Interceptor Order Coordination - Force
DgsWebFluxGraphQLInterceptorto run before subscription callback setup by setting@Order(Ordered.HIGHEST_PRECEDENCE). This was rejected as it may conflict with Apollo's design of running callback interceptor last. -
Lazy Instrumentation Initialization - Defer
GraphQLContextContributorInstrumentationlogic to a later phase. This was rejected as it requires larger refactoring and may break existing behavior. -
Try-catch wrapper - Wrap the
DgsContext.from()call in try-catch. This was rejected in favor of explicit null-safe API which is cleaner and more idiomatic.
The chosen solution (null-safe accessor) is minimal, backwards compatible, and explicit about the constraint.