What is WebGraphQlInterceptor and how do you use it to read an incoming HTTP header and make its value available to a data fetcher?
answer
- intercept(WebGraphQlRequest, Chain) -> Mono
- read getHeaders(), write GraphQLContext
- configureExecutionInput -> builder.graphQLContext
- fetcher reads via @ContextValue
- @Bean auto-detected; @Order sequences chain
basics
~20 sWebGraphQlInterceptor 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 sWebGraphQlInterceptor 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@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
Know it's a filter around the GraphQL request where you can read headers.
Show the intercept signature, reading getHeaders(), and passing a value via GraphQLContext to @ContextValue.
Explain why you must not use the servlet request in fetchers, response post-processing, ordering, and WebSocket handshake-only headers.
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