Spring Cloud Gateway — Interview Questions

Stack context: This system uses Spring Cloud Gateway as the single entry point (port 8080) for all six services. It runs on Spring WebFlux (Netty-based reactive), handles JWT session validation via Redis, applies per-user token-bucket rate limiting, and routes requests to downstream services. A local Caffeine cache (60 s, 10k entries) provides Redis fallback.


Q1 — What is Spring Cloud Gateway and what problem does it solve? junior

Answer: Spring Cloud Gateway (SCG) is a reactive API gateway built on Spring WebFlux and Project Reactor. It acts as the single entry point for all client requests in a microservices architecture, providing:

Why not use NGINX?: SCG is Java-native and integrates directly with Spring Security, Spring Cloud, and custom WebFilter beans — enabling business logic (like reading Redis sessions) in the gateway without a separate process.

This system: API Gateway (port 8080) routes to user-service (8081), product-service (8082), and order-service (8083). It validates JWT sessions from Redis before forwarding requests.


Q2 — What is Spring WebFlux and how does it differ from Spring MVC? junior

Answer:

Spring MVC Spring WebFlux
Threading model One thread per request (Tomcat) Event loop (Netty), few threads handle many requests
Programming model Blocking, imperative Non-blocking, reactive (Mono<T>, Flux<T>)
Backpressure No built-in Yes — via Project Reactor
I/O Blocking JDBC, blocking HTTP Non-blocking WebClient, reactive Redis
Memory efficiency Each thread ~1 MB stack ~100 bytes per async operation
Learning curve Simple Higher (reactive programming)

When to use WebFlux: High concurrency with many concurrent I/O-bound requests (the gateway validates Redis on every request). WebFlux reuses a small thread pool for all concurrency.

When to use MVC: Business logic services with complex domain operations, blocking DB calls — simpler code.

This system: Gateway uses WebFlux (required for SCG). All other services use MVC.


Q3 — What is a GatewayFilter vs a GlobalFilter? junior

Answer: Both intercept requests/responses, but their scope differs:

GlobalFilter: Applied to ALL routes automatically. Use for cross-cutting concerns that apply to every request (logging, authentication, tracing).

GatewayFilter: Applied only to specific routes. Use for route-specific transformations (strip prefix, add headers, rewrite path).

// GlobalFilter — applies to all routes
@Component
public class SessionValidationFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String token = extractToken(exchange);
        return validateSession(token)
            .flatMap(session -> chain.filter(injectHeaders(exchange, session)));
    }

    @Override
    public int getOrder() { return -1; } // run early (lower = earlier)
}

Built-in GatewayFilters (configured in YAML):


Q4 — How do you configure routes in Spring Cloud Gateway? junior

Answer: Routes are configured in application.yml or programmatically with RouteLocatorBuilder.

YAML configuration:

spring:
  cloud:
    gateway:
      routes:
        - id: order-service
          uri: http://order-service:8083
          predicates:
            - Path=/order-service/**
          filters:
            - StripPrefix=1          # remove /order-service prefix
            - AddRequestHeader=X-Gateway-Version, 1.0
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 10
                redis-rate-limiter.burstCapacity: 20
                key-resolver: "#{@userKeyResolver}"

Programmatic (Java):

@Bean
public RouteLocator routes(RouteLocatorBuilder builder) {
    return builder.routes()
        .route("product-service", r -> r
            .path("/product-service/**")
            .filters(f -> f.stripPrefix(1))
            .uri("http://product-service:8082"))
        .build();
}

This system: All routes defined in application.yml with StripPrefix=1 to remove the service prefix before forwarding.


Q5 — What are route predicates and what built-in predicates does SCG provide? junior

Answer: Predicates are conditions that must match for a route to be selected. Multiple predicates are AND-ed.

Built-in predicates:

Predicate Example Match condition
Path Path=/api/** Request path matches pattern
Method Method=GET,POST HTTP method
Header Header=X-Request-Id, \d+ Header exists and matches regex
Query Query=color, red Query parameter exists/matches
Host Host=**.example.com Host header matches pattern
After After=2026-01-01T00:00:00Z Current time is after date
RemoteAddr RemoteAddr=192.168.0.0/24 Client IP in CIDR range
Weight Weight=serviceA, 80 Weighted routing (A/B, canary)

Custom predicate: Implement RoutePredicateFactory<C> where C is a config class.

This system: Uses Path predicates exclusively. A Weight predicate would enable canary deployments.


Q6 — How does the RequestRateLimiter filter work in Spring Cloud Gateway? junior

Answer: The RequestRateLimiter filter applies a token-bucket rate limiter per key (user, IP, etc.) using Redis for distributed state.

Configuration:

filters:
  - name: RequestRateLimiter
    args:
      redis-rate-limiter.replenishRate: 10   # tokens added per second
      redis-rate-limiter.burstCapacity: 20   # max tokens (burst allowance)
      redis-rate-limiter.requestedTokens: 1  # tokens consumed per request
      key-resolver: "#{@userKeyResolver}"    # Bean that extracts the rate limit key

Key resolver (per-user rate limiting):

@Bean
public KeyResolver userKeyResolver() {
    return exchange -> Mono.justOrEmpty(
        exchange.getRequest().getHeaders().getFirst("X-User-Id")
    ).defaultIfEmpty("anonymous");
}

Response headers when rate limited:

This system: Rate limits by X-User-Id header injected by SessionValidationFilter before rate limiting runs.


Q7 — What is the reactive programming model in Project Reactor? junior

Answer: Project Reactor is the reactive library used by Spring WebFlux. It implements the Reactive Streams specification with two core types:

Key operators:

// Transform value
Mono<String> upper = redis.get("key").map(String::toUpperCase);

// Chain async operations
Mono<Order> order = userService.getUser(id)
    .flatMap(user -> orderService.createOrder(user, request));  // flatMap for async

// Error handling
Mono<Product> product = redis.get("product:" + id)
    .switchIfEmpty(Mono.error(new NotFoundException("Product not found")));

// Fallback
Mono<Session> session = redis.get("session:" + token)
    .switchIfEmpty(caffeine.get(token));  // fallback to local cache

Subscription model: Reactive pipelines are lazy — nothing executes until subscribed. subscribe(), block(), or returning a Mono/Flux from a WebFlux controller all trigger execution.


Q8 — How does ServerWebExchange work in Spring Cloud Gateway filters? junior

Answer: ServerWebExchange is the reactive equivalent of HttpServletRequest + HttpServletResponse. It holds the current request/response and allows modification via immutable builders.

public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    // Read request
    ServerHttpRequest request = exchange.getRequest();
    String token = request.getHeaders().getFirst("Authorization");
    String path = request.getPath().value();

    // Mutate request (add headers to forward)
    ServerHttpRequest mutatedRequest = request.mutate()
        .header("X-User-Id", session.getUserId())
        .header("X-User-Role", session.getRole())
        .build();

    // Mutate response (add response headers)
    exchange.getResponse().getHeaders().add("X-Gateway-Request-Id", UUID.randomUUID().toString());

    // Forward with mutated request
    return chain.filter(exchange.mutate().request(mutatedRequest).build());
}

Immutability: Request/response objects are immutable. All modifications create new instances via .mutate().build().


Q9 — What is Ordered interface and why does filter order matter? junior

Answer: Ordered interface sets the execution order of filters. Lower value = higher priority = runs earlier.

Why order matters:

@Component
@Order(-1)  // or implement Ordered
public class SessionValidationFilter implements GlobalFilter, Ordered {
    @Override
    public int getOrder() {
        return -1;
    }
}

Built-in filter orders: NettyRoutingFilter (order Integer.MAX_VALUE) runs last — it's the actual HTTP routing step. Custom filters should use negative values to run before routing.


Q10 — How do you handle circuit breaker pattern in Spring Cloud Gateway? senior

Answer: SCG integrates with Resilience4j for circuit breaker functionality via the CircuitBreaker filter.

How it works:

Configuration:

spring:
  cloud:
    gateway:
      routes:
        - id: order-service
          uri: http://order-service:8083
          filters:
            - name: CircuitBreaker
              args:
                name: orderServiceCB
                fallbackUri: forward:/fallback/order-service

Fallback endpoint:

@RestController
public class FallbackController {
    @GetMapping("/fallback/order-service")
    public Mono<ResponseEntity<ErrorResponse>> orderServiceFallback() {
        return Mono.just(ResponseEntity.status(503)
            .body(new ErrorResponse("SERVICE_UNAVAILABLE", "Order service is temporarily unavailable")));
    }
}

Q11 — How does WebClient differ from RestTemplate in a reactive context? senior

Answer:

RestTemplate WebClient
Threading Blocking — holds thread while waiting Non-blocking — releases thread during I/O
Programming model Synchronous Reactive (Mono<T>, Flux<T>)
WebFlux compatibility Blocks event loop if used in WebFlux Native — works correctly
HTTP/2 No Yes
Streaming Limited Full streaming support

Using WebClient in a Gateway filter:

@Autowired
private WebClient.Builder webClientBuilder;

public Mono<UserInfo> getUserInfo(String userId) {
    return webClientBuilder.build()
        .get()
        .uri("http://user-service:8081/internal/users/{id}", userId)
        .retrieve()
        .onStatus(HttpStatusCode::is4xxClientError,
            resp -> Mono.error(new UserNotFoundException(userId)))
        .bodyToMono(UserInfo.class)
        .timeout(Duration.ofSeconds(2));
}

Never use RestTemplate in a WebFlux application — it blocks the Netty event loop thread, causing thread starvation under load.


Q12 — What is path rewriting and how do you use RewritePath filter? junior

Answer: RewritePath uses regex substitution to transform the request path before forwarding to the upstream service.

Example: External path /api/v1/orders/{id} → internal path /orders/{id}

routes:
  - id: order-service-v1
    uri: http://order-service:8083
    predicates:
      - Path=/api/v1/orders/**
    filters:
      - RewritePath=/api/v1/orders/(?<segment>.*), /orders/${segment}

StripPrefix is simpler for prefix removal:

filters:
  - StripPrefix=2  # removes /api/v1 from /api/v1/orders/123 → /orders/123

Use cases:


Q13 — How do you implement CORS in Spring Cloud Gateway? junior

Answer: CORS (Cross-Origin Resource Sharing) must be configured at the gateway level. If individual services also configure CORS, duplicate headers can cause browser errors.

Gateway-level CORS:

spring:
  cloud:
    gateway:
      globalcors:
        corsConfigurations:
          '[/**]':
            allowedOrigins:
              - "https://app.example.com"
              - "http://localhost:3000"
            allowedMethods:
              - GET
              - POST
              - PUT
              - DELETE
              - OPTIONS
            allowedHeaders: "*"
            allowCredentials: true
            maxAge: 3600

Programmatic (more control):

@Bean
public CorsWebFilter corsWebFilter() {
    CorsConfiguration config = new CorsConfiguration();
    config.setAllowedOriginPatterns(List.of("https://*.example.com"));
    config.setAllowedMethods(List.of("*"));
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", config);
    return new CorsWebFilter(source);
}

Important: Disable CORS config in individual microservices to prevent duplicate Access-Control-Allow-Origin headers.


Q14 — What is the difference between pre-filter and post-filter in SCG? senior

Answer: Filters can execute logic before the request is routed (pre-filter) and/or after the response is received (post-filter).

@Component
public class RequestResponseLoggingFilter implements GlobalFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // PRE-FILTER: runs before forwarding
        log.info("Request: {} {}", exchange.getRequest().getMethod(), exchange.getRequest().getPath());
        long startTime = System.currentTimeMillis();

        return chain.filter(exchange)
            // POST-FILTER: runs after response received — flatMap on Mono completion
            .then(Mono.fromRunnable(() -> {
                long duration = System.currentTimeMillis() - startTime;
                log.info("Response: {} in {} ms",
                    exchange.getResponse().getStatusCode(), duration);
            }));
    }
}

Use cases:


Q15 — How would you implement request body logging in Spring Cloud Gateway without consuming the body? senior

Answer: In WebFlux, the request body is a Flux<DataBuffer> that can only be consumed once. To log it while still forwarding it, you must buffer and re-publish.

@Component
public class RequestBodyLoggingFilter implements GlobalFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        return DataBufferUtils.join(exchange.getRequest().getBody())
            .flatMap(dataBuffer -> {
                byte[] bytes = new byte[dataBuffer.readableByteCount()];
                dataBuffer.read(bytes);
                DataBufferUtils.release(dataBuffer);

                String body = new String(bytes, StandardCharsets.UTF_8);
                log.info("Request body: {}", body); // CAUTION: mask sensitive fields!

                // Re-wrap bytes into new request
                ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {
                    @Override
                    public Flux<DataBuffer> getBody() {
                        return Flux.just(exchange.getResponse().bufferFactory().wrap(bytes));
                    }
                };
                return chain.filter(exchange.mutate().request(mutatedRequest).build());
            });
    }
}

Warning: Logging request bodies can expose PII (passwords, card numbers). Always mask sensitive fields before logging. Consider whether logging at the gateway level is necessary or if service-level logging is more appropriate.


Q16 — How does load balancing work in Spring Cloud Gateway? senior

Answer: SCG integrates with Spring Cloud LoadBalancer for client-side load balancing. Instead of a static uri: http://order-service:8083, use the lb:// scheme:

routes:
  - id: order-service
    uri: lb://order-service   # service ID registered in discovery
    predicates:
      - Path=/order-service/**

Service discovery integration:

Load balancing algorithms:

This system: Uses static URIs (single instance per service in Docker Compose). lb:// would be used in a Kubernetes or Eureka-based deployment.


Q17 — How do you test a Spring Cloud Gateway route configuration? senior

Answer: Testing strategies from simplest to most comprehensive:

1. Unit test the filter logic with MockServerWebExchange:

@Test
void sessionFilterRejectsUnauthenticatedRequest() {
    MockServerWebExchange exchange = MockServerWebExchange.from(
        MockServerHttpRequest.get("/order-service/orders").build());
    GatewayFilterChain chain = e -> Mono.empty();

    sessionFilter.filter(exchange, chain).block();

    assertThat(exchange.getResponse().getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
}

2. Integration test with @WebFluxTest + WireMock:

@SpringBootTest(webEnvironment = RANDOM_PORT)
class GatewayRoutingTest {
    @Autowired WebTestClient webTestClient;

    // WireMock stubs the downstream service
    @Test
    void routesOrderRequestToOrderService() {
        wiremock.stubFor(get("/orders/123").willReturn(ok().withBody("{\"id\":\"123\"}")));
        webTestClient.get().uri("/order-service/orders/123")
            .header("Authorization", "Bearer " + validToken)
            .exchange()
            .expectStatus().isOk();
    }
}

This system: Uses WireMock (wiremock-spring-boot) to stub all downstream services in gateway integration tests, avoiding the need to start all 6 services.


Q18 — What is header-based routing and how would you implement API versioning with it? senior

Answer: Header-based routing selects routes based on HTTP request headers, enabling API versioning without URL changes.

routes:
  # V2 API — newer clients send Accept-Version: v2
  - id: product-service-v2
    uri: http://product-service-v2:8092
    predicates:
      - Path=/products/**
      - Header=Accept-Version, v2
    order: 1  # lower order = higher priority

  # V1 API — default for older clients
  - id: product-service-v1
    uri: http://product-service:8082
    predicates:
      - Path=/products/**
    order: 2

Versioning strategies:

Canary with Weight predicate: Route 10% of traffic to the new version for gradual rollout:

predicates:
  - Weight=product-service-group, 10  # 10% to V2

Q19 — How do you handle timeout and retry in Spring Cloud Gateway? senior

Answer: Global timeouts:

spring:
  cloud:
    gateway:
      httpclient:
        connect-timeout: 2000   # 2s to establish TCP connection
        response-timeout: 10s   # 10s to receive full response

Per-route timeouts (via Metadata filter):

routes:
  - id: slow-report-service
    uri: http://report-service:8090
    metadata:
      connect-timeout: 5000
      response-timeout: 60000   # reports can take up to 60s

Retry filter:

filters:
  - name: Retry
    args:
      retries: 3
      statuses: BAD_GATEWAY,SERVICE_UNAVAILABLE,GATEWAY_TIMEOUT
      methods: GET   # only retry idempotent methods
      backoff:
        firstBackoff: 100ms
        maxBackoff: 2000ms
        factor: 2

Caution: Never retry POST, PUT, DELETE unless the downstream service is idempotent. Retrying a non-idempotent request (e.g., place order) causes duplicate side effects.


Q20 — How would you implement request tracing and correlation IDs in the gateway? senior

Answer: A correlation ID allows tracing a request across all services in logs without Zipkin.

Implementation:

@Component
@Order(Integer.MIN_VALUE)   // run first
public class CorrelationIdFilter implements GlobalFilter {

    private static final String CORRELATION_HEADER = "X-Correlation-Id";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String correlationId = exchange.getRequest().getHeaders()
            .getFirst(CORRELATION_HEADER);

        if (correlationId == null) {
            correlationId = UUID.randomUUID().toString();
        }

        final String finalId = correlationId;

        // Add to MDC for gateway logs
        return chain.filter(
            exchange.mutate()
                .request(exchange.getRequest().mutate()
                    .header(CORRELATION_HEADER, finalId).build())
                .response(new ServerHttpResponseDecorator(exchange.getResponse()) {
                    @Override
                    public HttpHeaders getHeaders() {
                        super.getHeaders().add(CORRELATION_HEADER, finalId);
                        return super.getHeaders();
                    }
                })
                .build()
        );
    }
}

Downstream services read X-Correlation-Id and include it in all log statements. Combined with Zipkin's traceId, this provides both human-readable and machine-readable trace correlation.


Q21 — What is the ModifyRequestBody filter and when is it needed? senior

Answer: ModifyRequestBody allows transforming the request body before it reaches the downstream service. It's a built-in GatewayFilter factory.

Use cases:

@Bean
public RouteLocatorBuilder.Builder routeWithBodyModification(RouteLocatorBuilder builder) {
    return builder.routes()
        .route("order-service", r -> r
            .path("/order-service/**")
            .filters(f -> f.modifyRequestBody(
                String.class, String.class,
                MediaType.APPLICATION_JSON_VALUE,
                (exchange, body) -> {
                    // Inject X-User-Id from header into JSON body
                    JsonNode node = objectMapper.readTree(body);
                    ((ObjectNode) node).put("userId", exchange.getRequest().getHeaders().getFirst("X-User-Id"));
                    return Mono.just(objectMapper.writeValueAsString(node));
                }
            ))
            .uri("http://order-service:8083")
        );
}

Performance note: Body modification requires buffering the entire request body in memory. Avoid for large payloads; prefer injecting context via headers (which this system does).


Q22 — How do you secure actuator endpoints in Spring Cloud Gateway? senior

Answer: Actuator endpoints expose sensitive operational data. Secure them from external access:

1. Separate management port (expose only internally):

management:
  server:
    port: 8090   # different from 8080 (external)
  endpoints:
    web:
      exposure:
        include: "health,info,prometheus"

Network policy: block port 8090 from external internet; allow only monitoring systems.

2. Path restriction via gateway predicates:

routes:
  - id: actuator-block
    uri: no://op
    predicates:
      - Path=/*/actuator/**
    filters:
      - name: SetStatus
        args:
          status: 403
    order: -10  # before other routes

3. Spring Security on the gateway itself:

http.authorizeHttpRequests(auth -> auth
    .requestMatchers("/actuator/**").hasRole("OPS")
    .anyRequest().authenticated()
);

This system: Actuator endpoints are internal to the Docker network. Gateway blocks external access to /actuator/** paths.


Q23 — What is the DedupeResponseHeader filter? junior

Answer: DedupeResponseHeader removes duplicate headers from the response. This is commonly needed when both the gateway and downstream services add the same header (e.g., CORS headers).

filters:
  - DedupeResponseHeader=Access-Control-Allow-Origin Access-Control-Allow-Credentials, RETAIN_FIRST

Strategies:

When needed: When the gateway adds CORS headers AND the downstream service also adds them, browsers reject requests due to duplicate Access-Control-Allow-Origin headers. DedupeResponseHeader resolves this without removing CORS config from services.


Q24 — How do you implement a custom KeyResolver for rate limiting? junior

Answer: KeyResolver extracts the rate limit key from the ServerWebExchange. The key determines what is rate-limited (per user, per IP, per API key, etc.).

// Rate limit by user ID (authenticated requests)
@Bean
public KeyResolver userKeyResolver() {
    return exchange -> Mono.justOrEmpty(
        exchange.getRequest().getHeaders().getFirst("X-User-Id")
    ).defaultIfEmpty("anonymous");
}

// Rate limit by IP address
@Bean
public KeyResolver ipKeyResolver() {
    return exchange -> Mono.just(
        Objects.requireNonNull(exchange.getRequest().getRemoteAddress())
               .getAddress().getHostAddress()
    );
}

// Rate limit by API key from query parameter
@Bean
public KeyResolver apiKeyResolver() {
    return exchange -> Mono.justOrEmpty(
        exchange.getRequest().getQueryParams().getFirst("api_key")
    ).switchIfEmpty(Mono.error(new MissingApiKeyException()));
}

This system: Uses userKeyResolver() — authenticated users are rate limited by their user ID. Anonymous requests (if allowed) fall under the "anonymous" bucket (shared rate limit).


Q25 — What is the difference between using WebTestClient and MockMvc for testing WebFlux apps? junior

Answer:

MockMvc WebTestClient
Runtime Simulates the servlet layer (no server) Full reactive client (can test against real port)
Reactive support No Yes — handles Mono/Flux responses
Use with Spring MVC (@WebMvcTest) Spring WebFlux (@WebFluxTest or @SpringBootTest)
Real HTTP No (mock dispatch) Optional — can bind to mock or real server

WebTestClient for Gateway tests:

@SpringBootTest(webEnvironment = RANDOM_PORT)
class GatewayTest {
    @Autowired
    WebTestClient webTestClient;

    @Test
    void rateLimiterReturns429() {
        for (int i = 0; i < 25; i++) {
            webTestClient.get().uri("/products/123")
                .header("X-User-Id", "user-1")
                .exchange();
        }
        webTestClient.get().uri("/products/123")
            .header("X-User-Id", "user-1")
            .exchange()
            .expectStatus().isEqualTo(429);
    }
}

Q26 — How does Spring Cloud Gateway handle WebSocket proxying? senior

Answer: SCG supports WebSocket proxying transparently. WebSocket connections are established via an HTTP Upgrade handshake; SCG proxies the upgrade and then streams the bidirectional WebSocket frames.

Configuration (same as HTTP — SCG detects WebSocket via Upgrade header):

routes:
  - id: websocket-service
    uri: ws://chat-service:8086   # or wss:// for SSL
    predicates:
      - Path=/ws/**

Key considerations:


Q27 — What is sticky session routing and when is it needed in SCG? senior

Answer: Sticky session (session affinity) routes repeated requests from the same client to the same backend instance. Needed when a backend service maintains in-memory state per client.

SCG implementation with Cookie predicate + lb://:

routes:
  - id: stateful-service
    uri: lb://stateful-service
    predicates:
      - Cookie=INSTANCE_ID, .*   # route based on cookie
    filters:
      - name: LoadBalancerClientFilter
        args:
          # configures sticky sessions via StickySessionLoadBalancer

Or with StickySessionLoadBalancer:

@Bean
public ReactorLoadBalancer<ServiceInstance> stickySessionLoadBalancer(
        Environment environment,
        LoadBalancerClientFactory loadBalancerClientFactory) {
    String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
    return new StickySessionLoadBalancer(
        loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
}

Better practice: Design backend services to be stateless. Store state in Redis (as this system does for sessions) so any instance can handle any request.


Q28 — How do you debug routing issues in Spring Cloud Gateway? junior

Answer: Step-by-step debugging approach:

1. Enable route logging:

logging:
  level:
    org.springframework.cloud.gateway: TRACE
    reactor.netty: DEBUG

2. Check /actuator/gateway/routes (requires actuator exposure):

management.endpoints.web.exposure.include: "gateway"

GET /actuator/gateway/routes lists all configured routes with predicates and filters.

3. /actuator/gateway/globalfilters: Lists all global filters in execution order.

4. X-Forwarded-* headers: Inspect what headers the gateway adds to forwarded requests.

5. Common issues:


Q29 — How would you implement a gateway plugin for request/response body encryption? senior

Answer: End-to-end body encryption at the gateway decouples business services from encryption concerns.

Architecture:

  1. Client sends AES-encrypted body with Content-Encoding: encrypted.
  2. Gateway DecryptRequestBodyFilter decrypts and forwards plaintext to service.
  3. Service responds with plaintext.
  4. Gateway EncryptResponseBodyFilter encrypts and returns to client.
@Component
public class DecryptRequestBodyFilter implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        if (!isEncrypted(exchange)) return chain.filter(exchange);

        return DataBufferUtils.join(exchange.getRequest().getBody())
            .flatMap(buf -> {
                byte[] encrypted = readBytes(buf);
                byte[] decrypted = aesDecrypt(encrypted, getKeyForRequest(exchange));
                ServerHttpRequest mutated = exchange.getRequest().mutate()
                    .header(HttpHeaders.CONTENT_LENGTH, String.valueOf(decrypted.length))
                    .build();
                return chain.filter(exchange.mutate()
                    .request(new BodyInjectingRequest(mutated, decrypted)).build());
            });
    }
}

Security considerations: Key management is critical — use a KMS (AWS KMS, Azure Key Vault), not hardcoded keys. Rotate keys regularly. Log decryption failures for security monitoring.


Q30 — What are the production considerations for running Spring Cloud Gateway at scale? senior

Answer: Key production concerns for a high-traffic gateway:

1. Tuning Netty thread pool:

spring:
  cloud:
    gateway:
      httpclient:
        pool:
          max-connections: 1000     # per route
          acquire-timeout: 5000ms

2. Memory tuning: Gateway buffers request/response bodies for filters. Large payloads require higher heap. Set -Xmx512m and monitor GC pressure.

3. Redis connection pool: Rate limiter makes Redis calls on every request. Tune Lettuce pool (max-active: 20) for high throughput.

4. Health checks: Use /actuator/health for load balancer probes. Set readiness (routes configured + Redis connected) separate from liveness (process alive).

5. Horizontal scaling: Gateway is stateless (sessions are in Redis). Scale to N replicas behind a load balancer without sticky sessions.

6. Circuit breakers on all routes: Prevent a slow downstream service from cascading and blocking all gateway threads.

7. Observability: Export Micrometer metrics to Prometheus. Key metrics: gateway.requests (by route, status), gateway.requests.latency, Redis rate limiter latency.

8. Graceful shutdown: Configure server.shutdown=graceful and spring.lifecycle.timeout-per-shutdown-phase=30s to drain in-flight requests before shutdown.