DevNotes
Support
Microservices Fundamentals · Chapter 05

API Gateway with Spring Cloud Gateway

Why clients need one entry point, gateway responsibilities, routes, predicates and filters, load-balanced routes, authentication and token relay, rate limiting with Redis, circuit breakers and fallbacks, the Backend-for-Frontend pattern, and gateway pitfalls.

◷ 6 min read§ 10 sections? 6 interview questions{ } 3 code examples

#Introduction

Without a gateway, every client — web, mobile, partners — must know the address of every service, handle each one's authentication, and make many calls to build one screen. An API gateway is a single entry point that sits in front of all services.

Web, mobile and partner clients
API gateway — auth, routing, rate limits
Order, Product, User and Payment services

#Gateway Responsibilities

ResponsibilityExample
Routing/api/orders/** → order service, /api/products/** → product service
AuthenticationValidate the JWT once at the edge
Rate limiting100 requests per minute per API key
Load balancingSpread requests across service instances
ResilienceTimeouts, retries, circuit breakers, fallbacks
Cross-cutting headersAdd correlation IDs, strip internal headers, CORS
Protocol / payload translationREST ↔ gRPC, response aggregation
ObservabilityOne place to log and measure all incoming traffic
Hiding internalsClients never see internal service addresses or topology

#Spring Cloud Gateway

Spring Cloud Gateway is built on Spring WebFlux and Netty — non-blocking, so a few threads handle many concurrent connections.

XML
<!-- Spring Cloud 2025.0+; older versions use spring-cloud-starter-gateway -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway-server-webflux</artifactId>
</dependency>

Routes, Predicates and Filters

A route = an ID + a destination URI + predicates (when it matches) + filters (what to change):

YAML
spring:
  cloud:
    gateway:
      server:
        webflux:
          routes:
            - id: orders
              uri: lb://order-service              # lb:// = load-balanced via service discovery
              predicates:
                - Path=/api/orders/**
              filters:
                - StripPrefix=1                   # /api/orders/42 → /orders/42
                - AddRequestHeader=X-Gateway, spring-cloud-gateway

            - id: products
              uri: lb://product-service
              predicates:
                - Path=/api/products/**
                - Method=GET
Request GET /api/orders/42
Predicates — does Path=/api/orders/** match?
Pre-filters — authentication, rate limit, headers, strip prefix
Proxy to an order-service instance
Post-filters — response headers, logging
Response to the client
Common predicatesCommon filters
Path, Method, Host, Header, Query, Cookie, After/Before, WeightStripPrefix, RewritePath, AddRequestHeader, SetResponseHeader, Retry, CircuitBreaker, RequestRateLimiter, TokenRelay

Routes in Java

Java
@Bean
public RouteLocator routes(RouteLocatorBuilder builder) {
    return builder.routes()
            .route("payments", r -> r.path("/api/payments/**")
                    .filters(f -> f.stripPrefix(1)
                                   .circuitBreaker(c -> c.setName("payments").setFallbackUri("forward:/fallback/payments")))
                    .uri("lb://payment-service"))
            .build();
}

A Global Filter

Runs for every route — for example, adding a correlation ID:

Java
@Component
public class CorrelationIdFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String id = Optional.ofNullable(exchange.getRequest().getHeaders().getFirst("X-Correlation-Id"))
                            .orElse(UUID.randomUUID().toString());
        ServerHttpRequest request = exchange.getRequest().mutate().header("X-Correlation-Id", id).build();
        exchange.getResponse().getHeaders().add("X-Correlation-Id", id);
        return chain.filter(exchange.mutate().request(request).build());
    }

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

#Authentication at the Gateway

The gateway validates the user's token once, and passes it on:

YAML
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://auth.example.com/realms/shop   # e.g. Keycloak, Okta, Auth0
  • Reject unauthenticated requests at the edge.
  • Forward the token (Authorization header, or the TokenRelay filter when the gateway logs users in via OAuth2) so services can still check permissions.

#Rate Limiting

The built-in RequestRateLimiter uses Redis and a token-bucket algorithm:

YAML
filters:
  - name: RequestRateLimiter
    args:
      redis-rate-limiter.replenishRate: 10     # tokens added per second
      redis-rate-limiter.burstCapacity: 20     # maximum burst
      key-resolver: "#{@userKeyResolver}"
Java
@Bean
KeyResolver userKeyResolver() {
    return exchange -> Mono.justOrEmpty(exchange.getRequest().getHeaders().getFirst("X-API-Key"))
                           .defaultIfEmpty("anonymous");
}

Requests over the limit get 429 Too Many Requests. (Rate-limiting algorithms are covered in System Design Basics.)

#Resilience at the Edge

YAML
filters:
  - name: CircuitBreaker
    args:
      name: products
      fallbackUri: forward:/fallback/products
  - name: Retry
    args:
      retries: 2
      methods: GET          # retry only safe, idempotent methods
      statuses: BAD_GATEWAY, SERVICE_UNAVAILABLE

Set timeouts too (httpclient.connect-timeout, httpclient.response-timeout), so one slow service can't tie up the gateway.

#Backend for Frontend (BFF)

Different clients need different data shapes: a mobile app wants a compact payload, a web dashboard a rich one. The BFF pattern gives each client type its own gateway or thin API layer:

  • Clients
    • Web app → Web BFFaggregates, rich responses
    • Mobile app → Mobile BFFcompact payloads, fewer calls
    • Partners → Public API gatewayversioned, rate-limited

A BFF can also aggregate — call product, price and review services in parallel and return one response for a product page.

#Gateway Pitfalls

  • Business logic in the gateway — it becomes a new monolith every team depends on.
  • A single gateway instance — run several behind a load balancer; it's a critical path.
  • Blocking code in reactive filters.
  • No timeouts on downstream calls.
  • Trusting the gateway alone for security.

#Interview Questions

Q1What is an API gateway and why use one?

A single entry point in front of microservices that handles routing, authentication, rate limiting, load balancing, resilience and observability, so clients don't need to know the internal services.

Q2What are routes, predicates and filters in Spring Cloud Gateway?

A route maps matching requests to a destination URI; predicates decide whether a request matches (path, method, header…); filters modify the request or response before and after proxying (strip prefix, add headers, retry, circuit breaker, rate limit).

Q3What does lb:// mean in a route URI?

The destination is resolved through service discovery and load-balanced across instances by Spring Cloud LoadBalancer.

Q4How does rate limiting work in Spring Cloud Gateway?

The RequestRateLimiter filter uses Redis and a token-bucket algorithm keyed by a KeyResolver (user, API key, IP); requests over the limit receive 429.

Q5What is the Backend-for-Frontend pattern?

A separate gateway or API layer per client type (web, mobile, partners) that shapes and aggregates responses for that client.

Q6Should business logic live in the gateway?

No. Keep it to cross-cutting concerns; business logic belongs in services, otherwise the gateway becomes a bottleneck and a shared monolith.

#Key Takeaways

  • An API gateway is the single, secured entry point to your services.
  • Spring Cloud Gateway routes with predicates and filters; lb:// uses service discovery.
  • Authenticate at the edge, but keep authorization in services too.
  • Add rate limiting, timeouts, retries (for safe methods) and circuit breakers at the gateway.
  • Keep business logic out; use BFFs for client-specific shaping and aggregation.