Why Use Spring Cloud Gateway? A Beginner’s Guide to Building a SpringBoot API Gateway
In a micro‑service architecture, a centralized gateway eliminates the need for front‑ends to call dozens of services by handling authentication, rate‑limiting, logging, CORS, and security, and Spring Cloud Gateway—built on WebFlux and Netty—offers a non‑blocking, high‑throughput alternative to Zuul with detailed configuration and code examples for production use.
Why Use a Gateway?
Without a gateway, front‑ends must integrate with dozens or hundreds of service endpoints, scattering authentication, rate‑limiting, logging, CORS, and risk controls across services, leading to operational explosion and security loss of control.
Unified entry point for all clients (Web/APP/mini‑program)
Common cross‑cutting concerns (auth, token validation, CORS, logging, rate‑limiting, IP black/white list, risk interception) are written once in the gateway and shared by all services.
Fine‑grained traffic governance: load balancing, gray routing, weight distribution, request rewriting, path forwarding.
Security protection: block illegal requests, malicious parameters, unauthorized access.
Gateway Underlying Architecture
Technology stack
Underlying network framework: Netty
Programming model: Spring WebFlux (reactive, asynchronous, non‑blocking)
Container: Reactor
Features: single‑thread model, high concurrency, high throughput, low latency
Difference from Zuul
Zuul 1: Servlet‑based blocking model, one thread per request, low throughput, deprecated.
Spring Cloud Gateway: Non‑blocking asynchronous model, a few threads can handle massive requests, performance 5‑10× higher.
Complete execution chain
Client request → Netty receives request → Gateway handler → Predicate matches route → Pre‑filter → load‑balance selects instance → forward to business service → response returns → Post‑filter → response to client.
Three Core Components of Gateway
Route : the smallest unit, containing ID, URI, match rules, and filter list.
Predicate : Java 8 functional predicate that decides whether the current request matches the route; all predicates must pass for the route to be used.
Filter : divided into pre‑filter and post‑filter, used to modify request/response, intercept, log, and enforce rate‑limiting or authentication. Execution order: pre‑filter → business call → post‑filter.
Building a Production‑Grade Gateway from Scratch
Core Dependencies
Do NOT import spring-boot-starter-web to avoid startup conflicts.
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.15</version>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2021.0.5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2021.0.5.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- Gateway core -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- Nacos service discovery -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- Redis for rate limiting -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<!-- Sentinel for circuit breaking -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
</dependencies>Bootstrap Class
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class GatewayServerApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayServerApplication.class, args);
}
}YAML Configuration (routes, timeout, CORS, retry, rate‑limit)
server:
port: 8888
spring:
application:
name: gateway-server
redis:
host: 127.0.0.1
port: 6379
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
gateway:
httpclient:
connect-timeout: 2000
response-timeout: 5000
discovery:
locator:
enabled: false
lower-case-service-id: true
default-filters:
- name: Retry
args:
retries: 1
statuses: 500,502,503
routes:
- id: user-service-route
uri: lb://user-service
predicates:
- Path=/user/**
- Method=GET,POST
filters:
- StripPrefix=1
- AddRequestHeader=source,gateway
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burstCapacity: 20
key-resolver: "#{@ipKeyResolver}"
- id: order-service-route
uri: lb://order-service
predicates:
- Path=/order/**
filters:
- StripPrefix=1
globalcors:
cors-configurations:
'[/**]':
allowedOrigins: "*"
allowedMethods:
- GET
- POST
- PUT
- DELETE
allowedHeaders: "*"
allowCredentials: falseNine Common Predicate Examples
predicates:
- Path=/user/**
- Method=GET,POST
- Header=token,\w+
- Query=username
- RemoteAddr=127.0.0.1/24
- Host=**.test.com
- After=2024-01-01T00:00:00+08:00[Asia/Shanghai]
- Before=2099-01-01T00:00:00+08:00[Asia/Shanghai]
- Between=2024-01-01T00:00:00+08:00[Asia/Shanghai],2099-01-01T00:00:00+08:00[Asia/Shanghai]Common Built‑in Filters
filters:
- StripPrefix=1 # remove first path segment
- PrefixPath=/api # add a common prefix
- AddRequestHeader=gateway,yes
- AddResponseHeader=server,gateway
- SetStatus=201
- RedirectTo=302,https://www.baidu.comIP‑Based Rate‑Limit Key Resolver
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import reactor.core.publisher.Mono;
@Configuration
public class RateLimitConfig {
@Bean
public KeyResolver ipKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostString());
}
}Global Logging Filter
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Slf4j
@Configuration
public class LogFilterConfig {
@Bean
@Order(-100)
public GlobalFilter logGlobalFilter() {
return (exchange, chain) -> {
String path = exchange.getRequest().getPath().value();
String method = exchange.getRequest().getMethodValue();
String ip = exchange.getRequest().getRemoteAddress().getHostString();
log.info("[Gateway request] IP:{}, Method:{}, Path:{}", ip, method, path);
return chain.filter(exchange)
.doOnSuccess(v -> {
int code = exchange.getResponse().getStatusCode().value();
log.info("[Gateway response] Path:{}, Status:{}", path, code);
});
};
}
}Global Token Authentication Filter
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.core.annotation.Order;
import reactor.core.publisher.Mono;
import java.util.Arrays;
import java.util.List;
@Slf4j
@Configuration
public class AuthFilterConfig {
private static final List<String> WHITE_LIST = Arrays.asList("/user/login", "/user/register");
@Bean
@Order(0)
public GlobalFilter authFilter() {
return (exchange, chain) -> {
String path = exchange.getRequest().getPath().value();
if (WHITE_LIST.contains(path)) {
return chain.filter(exchange);
}
String token = exchange.getRequest().getHeaders().getFirst("token");
if (token == null || token.isEmpty()) {
log.error("Request missing token, blocked path: {}", path);
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
};
}
}Global Exception Handler (500)
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import reactor.core.publisher.Mono;
import java.util.HashMap;
import java.util.Map;
@ControllerAdvice
public class GatewayExceptionHandler {
@ExceptionHandler(Exception.class)
public Mono<Void> exceptionHandler(ServerHttpRequest request, ServerHttpResponse response, Exception e) {
response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
Map<String, Object> result = new HashMap<>();
result.put("code", 500);
result.put("msg", "Gateway service exception, please retry later");
result.put("path", request.getPath().value());
return response.writeWith(Mono.just(response.bufferFactory().wrap(result.toString().getBytes())));
}
}IP Black‑list Filter
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Arrays;
import java.util.List;
@Slf4j
@Configuration
public class BlackIpFilterConfig {
private static final List<String> BLACK_IP = Arrays.asList("192.168.1.88", "127.0.0.99");
@Bean
@Order(-50)
public GlobalFilter blackIpFilter() {
return (exchange, chain) -> {
String ip = exchange.getRequest().getRemoteAddress().getHostString();
if (BLACK_IP.contains(ip)) {
log.warn("Blocked black‑list IP: {}", ip);
exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
};
}
}Sentinel Circuit‑Breaker Configuration
spring:
cloud:
gateway:
routes:
- id: order-service-route
uri: lb://order-service
predicates:
- Path=/order/**
filters:
- StripPrefix=1
- name: CircuitBreaker
args:
name: orderCircuitBreaker
fallbackUri: forward:/gateway/fallbackFallback Controller for Circuit‑Breaker
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
public class GatewayFallbackController {
@GetMapping("/gateway/fallback")
public Map<String, Object> fallback() {
Map<String, Object> map = new HashMap<>();
map.put("code", 503);
map.put("msg", "Service busy, circuit‑breaker triggered, please retry later");
return map;
}
}Dynamic Routing
Static YAML routes require a gateway restart to take effect, whereas dynamic routing can be refreshed at runtime via a configuration center or database, supporting gray releases and emergency shutdowns. The core mechanism uses RouteDefinitionWriter to add, delete, or update routes without restarting.
Common Troubleshooting Tips
404 error: usually caused by missing StripPrefix, incorrect path matching, or service not registered.
503 error: service down, no instance in registry, or load‑balancer failure.
Startup conflict: importing spring-boot-starter-web causes dependency clash.
Rate limiting not effective: missing KeyResolver or Redis environment.
CORS errors: global CORS configuration missing or lower priority than other filters.
Key Takeaways
Spring Cloud Gateway is the official next‑generation gateway, built on WebFlux + Netty, offering far higher throughput than Zuul.
The three core components are Route, Predicate, and Filter.
Execution flow: request → predicate match → pre‑filter → load‑balance → post‑filter → response.
Gateway provides unified routing, load balancing, authentication, rate limiting, logging, CORS handling, and security protection.
Production deployments require clustering, dynamic governance, and comprehensive traffic protection to ensure high availability of the micro‑service entry point.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java Tech Workshop
Focused on Java backend technologies, sharing fundamentals, multithreading, JVM, the Spring ecosystem, microservices, distributed systems, high concurrency, source‑code analysis, and practical experience. Continuously delivers high‑quality original content, interview guides, and learning roadmaps to help Java developers progress from beginner to advanced, enhancing technical skills and core competitiveness.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
