dgs-framework icon indicating copy to clipboard operation
dgs-framework copied to clipboard

fix(subscription): handle missing DgsContext in subscription callbacks

Open amondnet opened this issue 1 month ago • 0 comments

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:

  • CallbackWebGraphQLInterceptor runs at LOWEST_PRECEDENCE (designed to run LAST)
  • GraphQLContextContributorInstrumentation.createState() is called BEFORE DgsWebFluxGraphQLInterceptor
  • 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:

  1. Add DgsContext.fromOrNull() method - A safe accessor that returns null instead of throwing NPE when DgsContext is not present
  2. 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
  • graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/context/GraphQLContextContributorInstrumentation.kt

    • Use null-safe DgsContext.fromOrNull() instead of DgsContext.from()
  • New test files:

    • graphql-dgs/src/test/kotlin/com/netflix/graphql/dgs/context/DgsContextTest.kt
    • graphql-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 requestData gracefully, but implementations already use nullable types

Alternatives considered

  1. Interceptor Order Coordination - Force DgsWebFluxGraphQLInterceptor to 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.

  2. Lazy Instrumentation Initialization - Defer GraphQLContextContributorInstrumentation logic to a later phase. This was rejected as it requires larger refactoring and may break existing behavior.

  3. 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.

amondnet avatar Nov 28 '25 07:11 amondnet