Spring Boot Tutorial
API Gateway with Spring Cloud Gateway
Clients should not need to know that your system consists of fifteen services on different hosts. An API gateway gives them one entry point: it routes each request to the right service and handles cross-cutting concerns at the edge — authentication, rate limiting, CORS, request/response rewriting, retries and circuit breaking.
Spring Cloud Gateway is Spring's API gateway, available in a reactive (WebFlux, Netty) variant and a servlet (Spring MVC) variant. This lesson builds a WebFlux-based gateway with routes, predicates and filters, JWT validation at the edge, Redis-backed rate limiting, circuit breakers with fallbacks, and discovery-based routing.
Routes, Predicates and Filters
A route has an id, a destination uri, predicates that decide whether a request matches (Path, Method, Host, Header, Query, Weight for canary releases), and filters that modify the request or response (StripPrefix, RewritePath, AddRequestHeader, RequestRateLimiter, CircuitBreaker, Retry). Routes are defined in YAML under spring.cloud.gateway.server.webflux.routes or in Java with RouteLocatorBuilder.
Choosing a Variant
spring-cloud-starter-gateway-server-webflux runs on Netty and handles very high concurrency with few threads — the classic choice. spring-cloud-starter-gateway-server-webmvc runs on the servlet stack, which suits teams that prefer blocking code (especially with virtual threads). The concepts are the same; the configuration prefixes differ (...server.webflux... vs ...server.webmvc...).
Security at the Edge
Configure the gateway as an OAuth2 resource server to reject requests without a valid JWT before they reach any service, and relay the token downstream (TokenRelay filter when the gateway is also the OAuth2 client for a browser app). Downstream services should still validate tokens themselves — "defence in depth" — rather than trusting anything that comes from inside the network.
Rate Limiting and Resilience
The RequestRateLimiter filter with the Redis rate limiter implements a token bucket per key (user, API key or IP) that works across multiple gateway instances. The CircuitBreaker filter (Resilience4j) returns a fallback when a service is failing, and the Retry filter retries idempotent requests on transient errors.
Examples
Gateway routes in YAML
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway-server-webflux</artifactId>
</dependency>
# application.yml
server:
port: 8080
spring:
cloud:
gateway:
server:
webflux:
routes:
- id: catalog
uri: http://catalog-service:8081
predicates:
- Path=/api/catalog/**
filters:
- RewritePath=/api/catalog/(?<rest>.*), /api/products/${rest}
- id: orders
uri: http://order-service:8082
predicates:
- Path=/api/orders/**
- Method=GET,POST
filters:
- AddRequestHeader=X-Gateway, webnest
- id: new-checkout-canary
uri: http://checkout-v2:8090
predicates:
- Path=/api/checkout/**
- Weight=checkout, 10 # 10% of traffic to v2
- id: checkout-stable
uri: http://checkout-v1:8089
predicates:
- Path=/api/checkout/**
- Weight=checkout, 90
GET http://gateway:8080/api/catalog/42 -> forwarded to http://catalog-service:8081/api/products/42
POST http://gateway:8080/api/orders -> forwarded to order-service with header X-Gateway: webnest
/api/checkout/** -> ~90% to checkout-v1, ~10% to checkout-v2
JWT validation, rate limiting and circuit breaking
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId>
</dependency>
@Configuration
@EnableWebFluxSecurity
public class GatewaySecurity {
@Bean
SecurityWebFilterChain security(ServerHttpSecurity http) {
return http
.authorizeExchange(ex -> ex
.pathMatchers("/api/catalog/**").permitAll()
.anyExchange().authenticated())
.oauth2ResourceServer(o -> o.jwt(Customizer.withDefaults()))
.csrf(ServerHttpSecurity.CsrfSpec::disable)
.build();
}
// rate-limit per authenticated user
@Bean
KeyResolver userKeyResolver() {
return exchange -> exchange.getPrincipal().map(Principal::getName).defaultIfEmpty("anonymous");
}
}
# application.yml (route filters)
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://auth-server:9000
cloud:
gateway:
server:
webflux:
routes:
- id: orders
uri: http://order-service:8082
predicates:
- Path=/api/orders/**
filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10 # tokens per second
redis-rate-limiter.burstCapacity: 20
key-resolver: "#{@userKeyResolver}"
- name: CircuitBreaker
args:
name: orders
fallbackUri: forward:/fallback/orders
@RestController
class FallbackController {
@GetMapping("/fallback/orders")
Mono<ResponseEntity<Map<String, String>>> ordersDown() {
return Mono.just(ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE)
.body(Map.of("message", "Orders are temporarily unavailable. Please try again shortly.")));
}
}
GET /api/orders (no token) -> 401 Unauthorized (rejected at the gateway)
GET /api/orders (valid JWT, 25 req/s) -> first 20 OK, then 429 Too Many Requests
GET /api/orders (order-service down) -> 503 {"message":"Orders are temporarily unavailable. Please try again shortly."}
Routes in Java and discovery-based URIs
@Bean
RouteLocator routes(RouteLocatorBuilder builder) {
return builder.routes()
.route("payments", r -> r.path("/api/payments/**")
.filters(f -> f.retry(c -> c.setRetries(2).setMethods(HttpMethod.GET)))
.uri("lb://payment-service")) // resolved through Eureka / LoadBalancer
.build();
}
GET /api/payments/pay_91x -> lb://payment-service -> 10.0.0.12:53120 (retried on another instance if it fails)
Common Mistakes
- Using the old spring.cloud.gateway.routes prefix; current versions use spring.cloud.gateway.server.webflux.routes (or ...webmvc...).
- Putting business logic in the gateway, turning it into a new monolith.
- Validating JWTs only at the gateway and trusting all internal traffic blindly.
- Retrying non-idempotent POST requests at the gateway.
- Deploying the WebFlux gateway as a WAR or into a servlet container — it requires Netty.
Key Points to Remember
- Spring Cloud Gateway routes requests using predicates and modifies them with filters.
- Choose the WebFlux (Netty) or WebMVC variant; routes live under spring.cloud.gateway.server.<variant>.routes.
- Validate JWTs at the edge and again in services.
- RequestRateLimiter with Redis and CircuitBreaker with fallbacks protect services.
- lb:// URIs route through service discovery; Weight predicates enable canary releases.
Practice the examples
Change an input, predict the result, then compare it with the output. Explain why the result changes.
Use your local JDK or project IDE for these examples. Codelab currently runs Python and HTML/CSS/JavaScript; framework examples may need project dependencies.