skip to content

What is WebGraphQlInterceptor and how do you use it to read an incoming HTTP header and make its value available to a data fetcher?

level: seniorimportance: must knowfreq 34%

answer

  1. intercept(WebGraphQlRequest, Chain) -> Mono
  2. read getHeaders(), write GraphQLContext
  3. configureExecutionInput -> builder.graphQLContext
  4. fetcher reads via @ContextValue
  5. @Bean auto-detected; @Order sequences chain

basics

~20 s

WebGraphQlInterceptor is a bean that wraps every web GraphQL request. In intercept(request, chain) you read request.getHeaders(), put a value into the GraphQLContext (via request.configureExecutionInput or an attribute), then a data fetcher reads it with @ContextValue.

solid answer

~40 s

WebGraphQlInterceptor is Spring for GraphQL's transport-level filter for HTTP and WebSocket requests — the bridge between web concerns (headers, cookies) and the graphql-java execution. You implement Mono<WebGraphQlResponse> intercept(WebGraphQlRequest request, Chain chain). To propagate a header, you read request.getHeaders(), then write the value into the GraphQLContext — commonly via request.configureExecutionInput((input, builder) -> builder.graphQLContext(ctx -> ctx.put("tenantId", value))) — then call chain.next(request). Downstream, a controller method injects it with @ContextValue String tenantId or reads DataFetchingEnvironment.getGraphQlContext(). You can also mutate the outgoing response (e.g. set a response header) after the chain returns. Register it simply by declaring it as a @Bean; multiple interceptors run as an ordered chain (use Ordered/@Order). This is the idiomatic place for auth, tenant resolution, tracing, and locale propagation.

code

java · 22 lines
java
@Component
class TenantInterceptor implements WebGraphQlInterceptor {

    @Override
    public Mono<WebGraphQlResponse> intercept(WebGraphQlRequest request, Chain chain) {
        String tenantId = request.getHeaders().getFirst("X-Tenant-Id");
        // stash the header value into the per-execution GraphQLContext
        request.configureExecutionInput((executionInput, builder) ->
            builder.graphQLContext(Map.of("tenantId", tenantId)).build());

        return chain.next(request).doOnNext(response ->
            response.getResponseHeaders().add("X-Handled-By", "tenant-interceptor"));
    }
}

@Controller
class OrderController {
    @QueryMapping
    public List<Order> orders(@ContextValue String tenantId) {   // pulled from GraphQLContext
        return orderService.forTenant(tenantId);
    }
}

go deeper

for a junior

Know it's a filter around the GraphQL request where you can read headers.

for a middle

Show the intercept signature, reading getHeaders(), and passing a value via GraphQLContext to @ContextValue.

for a senior

Explain why you must not use the servlet request in fetchers, response post-processing, ordering, and WebSocket handshake-only headers.

for a principal

Discuss context-propagation across threads (Micrometer/ThreadLocalAccessor), auth/tenant architecture, and interceptor vs instrumentation layering trade-offs.

**The problem it solves.** graphql-java, the engine underneath, knows nothing about HTTP — no headers, cookies, or `HttpServletRequest`. `WebGraphQlInterceptor` is the **transport-level component** that lets you take web request data and inject it into GraphQL execution, and to post-process the result. It applies to both the **HTTP** and **WebSocket** transports (there is a separate `RSocketGraphQlInterceptor` for RSocket). **The interface.** ```java public interface WebGraphQlInterceptor { Mono<WebGraphQlResponse> intercept(WebGraphQlRequest request, Chain chain); interface Chain { Mono<WebGraphQlResponse> next(WebGraphQlRequest request); } } ``` It is a **reactive** filter (`Mono`), even in a Web MVC app — Spring adapts it. `WebGraphQlRequest` extends the GraphQL request and adds web accessors: `getHeaders()` (an `HttpHeaders`), `getCookies()`, `getUri()`, `getAttributes()`, and (for HTTP) methods to set response state. **Propagating a header into execution — the two common patterns:** 1. **Configure the GraphQLContext** (recommended for values fetchers need): ```java String tenant = request.getHeaders().getFirst("X-Tenant-Id"); request.configureExecutionInput((executionInput, builder) -> builder.graphQLContext(Collections.singletonMap("tenantId", tenant)).build()); return chain.next(request); ``` The **`GraphQLContext`** is a per-execution key/value map that graphql-java threads through every data fetcher. Anything you put here is retrievable downstream. 2. **Read it in a controller** with **`@ContextValue`**: ```java @QueryMapping public List<Order> orders(@ContextValue String tenantId) { ... } ``` Or from a raw fetcher via `DataFetchingEnvironment#getGraphQlContext().get("tenantId")`. **Modifying the response.** Because you control the chain, you can act *after* execution: ```java return chain.next(request).doOnNext(response -> response.getResponseHeaders().add("X-Trace-Id", traceId)); ``` (`getResponseHeaders()` is available on the HTTP response object.) You can also inspect/rewrite errors before they reach the client. **Registration & ordering.** Just declare it as a `@Bean`; Boot auto-detects all `WebGraphQlInterceptor` beans and assembles them into a chain that wraps the execution. When you have several, control order by implementing `Ordered` or annotating with `@Order` — lower value runs earlier (outermost). The chain is like a nest of filters: each interceptor sees the request on the way in and the response on the way out. **Context propagation to blocking/reactive fetchers.** Values placed in the `GraphQLContext` reach fetchers regardless of threading. For **ThreadLocal**-based context (e.g. Spring Security's `SecurityContext`, MDC), Spring for GraphQL uses the Micrometer **context-propagation** library and registered `ThreadLocalAccessor`s to bridge values across reactive/async boundaries — so a header captured in an interceptor and stashed appropriately is visible even when a fetcher runs on a different thread. **Gotchas / when-to-use.** - **Don't reach for `HttpServletRequest` inside a data fetcher.** The fetcher may run on another thread and the servlet request may be gone. Capture web data in the interceptor and pass it via the `GraphQLContext`. - On **WebSocket**, HTTP headers exist only at the handshake/`connection_init`; per-operation header reads won't work after the connection is open — resolve auth/context once at connection time. - `configureExecutionInput` may be called multiple times if multiple interceptors configure it; they compose. - Interceptor runs for the whole request, not per field — for per-field cross-cutting use graphql-java instrumentation or `@SchemaMapping`-level logic. - Ideal uses: authentication/authorization context, multi-tenant resolution, request tracing/correlation IDs, locale, and setting response headers.

  • Why not just autowire HttpServletRequest inside the data fetcher and read the header there?
    Data fetchers can execute on a different thread (async/reactive), where the ThreadLocal-bound servlet request may be unavailable or already recycled. The interceptor runs on the transport thread with guaranteed access; capturing there and passing via GraphQLContext is thread-safe and transport-agnostic (also works for WebSocket/RSocket).
  • You register two WebGraphQlInterceptors and order matters. How do you control it?
    Make each implement Ordered or annotate with @Order; the lower value runs earlier and is outermost in the chain, so it sees the request first and the response last. Spring assembles all detected beans into that ordered chain.
  • How does a value captured in an interceptor reach a Spring Security @PreAuthorize check or MDC on a different thread?
    Via Micrometer context-propagation and registered ThreadLocalAccessors. Spring for GraphQL bridges reactive Context and ThreadLocals (SecurityContext, MDC) across async boundaries so the captured context is restored where the fetcher/authorization runs.

saying these in an interview costs you the question

  • Reading headers directly inside the data fetcher via HttpServletRequest
  • Thinking the interceptor runs once per field rather than per request
  • Believing you must manually register it in a config list (it's auto-detected as a bean)
  • Confusing WebGraphQlInterceptor with graphql-java Instrumentation (different layer)
  • Expecting per-message HTTP headers on an already-open WebSocket connection

context