Microservices API Gateway Pattern
The front door to your microservices. In modern software architecture, microservices have become a popular way to build scalable and flexible applications. Instead of one large application, you have many small, independent services, each responsible for one business function. But how do clients talk to all of them? The answer is the API Gateway pattern.
This guide explains what an API gateway is, what it does, and when to use it. You will then build a working gateway with Spring Cloud Gateway, step by step: routing, filters, security, rate limiting, fault tolerance and response aggregation.
1. The Problem: Clients Talking to Many Services
Imagine an online shop built from four microservices: users, products, orders and inventory. Without a gateway, the mobile app must know about and call every one of them directly.
| Problem | What goes wrong |
|---|---|
| Many addresses to know | The client needs the location of every service, and breaks when one moves or is split |
| Many round trips | One screen needs several network calls, which is slow on mobile networks |
| Repeated cross-cutting code | Every service must check tokens, limit traffic and log requests on its own |
| Internal details exposed | Internal services are reachable from the internet, which increases the attack surface |
| Hard to change | Renaming or splitting a service forces an update of every client app |
An API gateway solves these problems by putting one entry point in front of all services.
2. What Is an API Gateway?
An API gateway is a server that acts as the single entry point for all client requests. It sits between the clients (web apps, mobile apps, partner systems) and your backend services. Clients talk only to the gateway; the gateway forwards each request to the right service and returns the response.
Web app Mobile app Partner
\ | /
\ | /
+---------+---------+
|
[ API Gateway ]
|
+---------+---+-----+----------+
| | | |
Users Products Orders Inventory
3. How a Request Flows Through the Gateway
Every request passes through the same steps inside the gateway:
- Receive the request from the client, for example
GET /api/orders/42. - Authenticate the caller by checking their token.
- Apply policies such as rate limits.
- Route: find the matching rule and pick the target service, here the order service.
- Transform the request if needed, for example add headers or rewrite the path.
- Forward the request to a healthy instance of the service, with a timeout.
- Return the response to the client, possibly transformed, and record logs and metrics.
4. Core Responsibilities of an API Gateway
| # | Responsibility | What it means |
|---|---|---|
| 1 | Request routing | Sends each request to the correct service based on the URL path, HTTP method or headers |
| 2 | API composition and aggregation | Combines data from several services into one response, so the client makes one call instead of many |
| 3 | Authentication and authorization | Checks API keys, JSON Web Tokens (JWT) or other credentials before requests reach the services |
| 4 | Rate limiting and throttling | Protects services from being overwhelmed by too many requests from one client |
| 5 | Caching | Stores frequent responses to reduce load and improve response times |
| 6 | Load balancing | Spreads requests across several instances of a service |
| 7 | Logging and monitoring | Records every request and collects metrics, giving one place to watch API health |
| 8 | Transformation and protocol translation | Converts between formats and protocols, for example REST for clients and gRPC inside |
Other common jobs include CORS handling, SSL/TLS termination (handling HTTPS so internal services do not have to), request validation and adding correlation IDs for tracing.
The key idea: these are cross-cutting concerns that every service would otherwise implement separately. Moving them to the gateway keeps each service focused on its business logic.
5. A Concrete Example: The E-Commerce Application
Our example shop has four microservices:
- User service: manages user accounts and profiles.
- Product service: handles product information and details.
- Order service: processes orders and payments.
- Inventory service: tracks product stock levels.
The mobile app needs to show a customer’s order history, including product names and current stock.
5.1 Scenario 1: without an API gateway
- Call the user service to check the login.
- Call the order service for the list of orders.
- Call the product service once for each product in those orders.
- Call the inventory service for stock levels.
- Combine all the data inside the app.
Result: many direct calls over a slow mobile network, poor performance and complicated client code that breaks whenever a service changes.
5.2 Scenario 2: with an API gateway
The app makes one request: GET /my/order-history. Behind the scenes:
- The mobile app sends the request with its authentication token.
- The gateway validates the token.
- The gateway calls the order, product and inventory services over the fast internal network, in parallel where possible.
- It combines the responses into a single JSON object.
- It returns that one response to the app.
Result: faster screens, simpler client code and a better user experience. The services can change internally without the app noticing.
6. Building a Gateway with Spring Cloud Gateway
Spring Cloud Gateway is the gateway from the Spring family. It is built on Spring Boot, so it is a natural choice for Java teams. A gateway is simply a separate Spring Boot application.
6.1 Dependency
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway-server-webflux</artifactId>
</dependency>
Also import the Spring Cloud BOM (spring-cloud-dependencies) in <dependencyManagement> so that versions match your Spring Boot version.
6.2 Routes in configuration
A route has three parts: an ID, a predicate that decides which requests match, and a URI to forward them to. Filters can change the request or response on the way.
server:
port: 8080
spring:
application:
name: shop-gateway
cloud:
gateway:
server:
webflux:
routes:
- id: product-service
uri: http://localhost:8081
predicates:
- Path=/api/products/**
filters:
- StripPrefix=1 # /api/products/7 -> /products/7
- id: order-service
uri: http://localhost:8082
predicates:
- Path=/api/orders/**
filters:
- StripPrefix=1
- id: user-service
uri: http://localhost:8083
predicates:
- Path=/api/users/**
filters:
- StripPrefix=1
Clients now call only port 8080. A request to http://localhost:8080/api/products/7 is forwarded to the product service as /products/7.
| Common predicate | Matches when |
|---|---|
Path=/api/orders/** | The URL path matches the pattern |
Method=GET,POST | The HTTP method is one of those listed |
Header=X-Api-Version, 2 | A header has a given value |
Host=**.shop.example.com | The host name matches |
Query=category | A query parameter is present |
Weight=orders, 90 | Used to send a share of traffic, for example 90%, to one version |
uri: lb://product-service instead of a fixed address, and the gateway load-balances across all running instances. Version note: Spring Cloud 2025.0 renamed the starter to spring-cloud-starter-gateway-server-webflux and moved route properties under spring.cloud.gateway.server.webflux. Older versions use spring-cloud-starter-gateway and spring.cloud.gateway.routes.7. Defining Routes in Java
The same routes can be written in Java, which is handy when you need logic or want compile-time checking:
package com.example.gateway.config;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
@Configuration
public class GatewayRoutesConfig {
@Bean
public RouteLocator shopRoutes(RouteLocatorBuilder builder) {
return builder.routes()
// Anyone may read products
.route("product-service", r -> r
.path("/api/products/**")
.and().method(HttpMethod.GET)
.filters(f -> f
.stripPrefix(1)
.addResponseHeader("Cache-Control", "max-age=60"))
.uri("lb://product-service"))
// Orders, with a fallback if the service is down
.route("order-service", r -> r
.path("/api/orders/**")
.filters(f -> f
.stripPrefix(1)
.circuitBreaker(cb -> cb
.setName("orders")
.setFallbackUri("forward:/fallback/orders")))
.uri("lb://order-service"))
// Send 10% of user traffic to the new version (canary release)
.route("user-service-v2", r -> r
.path("/api/users/**")
.and().weight("users", 10)
.filters(f -> f.stripPrefix(1))
.uri("lb://user-service-v2"))
.route("user-service-v1", r -> r
.path("/api/users/**")
.and().weight("users", 90)
.filters(f -> f.stripPrefix(1))
.uri("lb://user-service"))
.build();
}
}
The weighted routes show a common gateway trick: a canary release. A new version receives a small share of real traffic first, and you increase it once you are confident it works.
8. Filters: Changing Requests and Responses
8.1 Built-in filters
| Filter | What it does |
|---|---|
StripPrefix=1 | Removes the first part of the path, such as /api |
RewritePath=/api/(?<rest>.*), /${rest} | Rewrites the path with a regular expression |
AddRequestHeader=X-Source, gateway | Adds a header before forwarding |
AddResponseHeader=X-Served-By, shop-gateway | Adds a header to the response |
Retry=3 | Retries failed requests, best used for safe GET calls |
RequestRateLimiter | Limits requests per client (see section 10) |
CircuitBreaker | Stops calling a failing service and uses a fallback (see section 11) |
8.2 A custom global filter: correlation IDs
A global filter runs for every route. This one gives each request a correlation ID, so the same request can be followed through the logs of every service:
package com.example.gateway.filter;
import java.util.UUID;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class CorrelationIdFilter implements GlobalFilter, Ordered {
private static final String HEADER = "X-Correlation-Id";
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String incoming = exchange.getRequest().getHeaders().getFirst(HEADER);
String correlationId = (incoming != null) ? incoming : UUID.randomUUID().toString();
// Pass the ID on to the backend service...
ServerHttpRequest request = exchange.getRequest().mutate()
.header(HEADER, correlationId)
.build();
// ...and return it to the client, which helps with support requests
exchange.getResponse().getHeaders().add(HEADER, correlationId);
return chain.filter(exchange.mutate().request(request).build());
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE; // run before other filters
}
}
Spring Cloud Gateway (WebFlux edition) is built on Spring WebFlux, so filters return a Mono. Avoid blocking calls, such as plain JDBC, inside filters, because they would stall the gateway’s small pool of threads.
9. Securing APIs at the Gateway
Checking tokens at the gateway means unauthenticated requests are rejected before they reach any service. Add Spring Security’s resource server support:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://login.example.com/realms/shop
package com.example.gateway.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;
@Configuration
@EnableWebFluxSecurity
public class GatewaySecurityConfig {
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange(exchange -> exchange
// Product browsing is public
.pathMatchers(HttpMethod.GET, "/api/products/**").permitAll()
.pathMatchers("/actuator/health").permitAll()
// Everything else needs a valid token
.anyExchange().authenticated()
)
.oauth2ResourceServer(oauth -> oauth.jwt(Customizer.withDefaults()))
// Stateless token API: no cookies, so CSRF protection is not needed
.csrf(ServerHttpSecurity.CsrfSpec::disable);
return http.build();
}
}
The gateway forwards the Authorization header to the services, so they can still read the user’s identity and make their own detailed decisions, such as “customers may only see their own orders”.
10. Rate Limiting
Rate limiting protects your services from overload, abuse and runaway clients. Spring Cloud Gateway includes a Redis-based limiter using the token bucket algorithm. Add the reactive Redis starter (spring-boot-starter-data-redis-reactive), then configure the filter on a route:
filters:
- StripPrefix=1
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10 # tokens added per second
redis-rate-limiter.burstCapacity: 20 # maximum short burst
key-resolver: "#{@clientIpKeyResolver}"
The key resolver decides what counts as “one client”. Here it is the caller’s IP address:
package com.example.gateway.config;
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 clientIpKeyResolver() {
return exchange -> Mono.justOrEmpty(exchange.getRequest().getRemoteAddress())
.map(address -> address.getAddress().getHostAddress())
.defaultIfEmpty("unknown");
}
}
Each client may make about 10 requests per second, with short bursts up to 20. Extra requests receive 429 Too Many Requests. For logged-in users, limiting by user ID (taken from the token) is usually fairer than limiting by IP address, because many users can share one IP.
11. Timeouts, Circuit Breakers and Fallbacks
The gateway calls every service, so one slow service could tie up the gateway and hurt everyone. Protect it with timeouts and circuit breakers.
spring:
cloud:
gateway:
server:
webflux:
httpclient:
connect-timeout: 2000 # milliseconds
response-timeout: 5s
The circuit breaker used in section 7 needs the Resilience4j starter, spring-cloud-starter-circuitbreaker-reactor-resilience4j. When the order service fails repeatedly, the circuit “opens” and requests go straight to the fallback instead of waiting for timeouts:
package com.example.gateway.fallback;
import java.util.Map;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class FallbackController {
@RequestMapping("/fallback/orders")
public ResponseEntity<Map<String, String>> ordersFallback() {
return ResponseEntity
.status(HttpStatus.SERVICE_UNAVAILABLE)
.body(Map.of("message", "Orders are temporarily unavailable. Please try again in a moment."));
}
}
Clients get a fast, friendly answer instead of a long wait and a cryptic error. Learn more in the Circuit Breaker Pattern guide.
12. Aggregating Responses from Several Services
Routing forwards one request to one service. For screens that need data from several services, the gateway can call them in parallel and combine the results. This implements scenario 2 from section 5 in a simplified form:
package com.example.gateway.aggregation;
import java.util.List;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
@RestController
public class DashboardController {
private final WebClient webClient;
public DashboardController(WebClient.Builder builder) {
this.webClient = builder.build();
}
@GetMapping("/my/dashboard")
public Mono<Dashboard> dashboard(@AuthenticationPrincipal Jwt jwt) {
String userId = jwt.getSubject();
Mono<UserProfile> profile = webClient.get()
.uri("http://localhost:8083/users/{id}", userId)
.headers(h -> h.setBearerAuth(jwt.getTokenValue()))
.retrieve()
.bodyToMono(UserProfile.class);
Mono<List<OrderSummary>> recentOrders = webClient.get()
.uri("http://localhost:8082/orders?customerId={id}&limit=5", userId)
.headers(h -> h.setBearerAuth(jwt.getTokenValue()))
.retrieve()
.bodyToFlux(OrderSummary.class)
.collectList();
// Both calls run at the same time; the result is ready when both finish
return Mono.zip(profile, recentOrders, Dashboard::new);
}
public record UserProfile(String id, String name, String email) {
}
public record OrderSummary(Long id, String status, double total) {
}
public record Dashboard(UserProfile profile, List<OrderSummary> recentOrders) {
}
}
Keep aggregation simple: combining and reshaping data is fine, but real business rules belong in the services. If aggregation logic grows large, move it into a dedicated service or a backend for frontend.
13. Backend for Frontend (BFF)
Different clients often need different APIs. A mobile app wants small responses to save data, while a web dashboard wants rich, detailed ones. One gateway that serves everyone can become cluttered with special cases.
The Backend for Frontend (BFF) pattern solves this by giving each type of client its own gateway:
Mobile app Web app Partners
| | |
[Mobile BFF] [Web BFF] [Partner API]
\ | /
+------------+------------+
|
Users Products Orders Inventory
| One shared gateway | Backend for frontend | |
|---|---|---|
| Best for | A few similar clients | Very different clients, such as mobile, web and partners |
| Owned by | A platform team | Usually the team that builds that client |
| Responses | Generic | Tailored to each client’s screens |
| Downside | Can become crowded with client-specific logic | More gateways to run and maintain |
Many systems combine both: a shared edge gateway handles security and rate limiting, and small BFFs behind it shape the data for each client.
14. API Gateway vs Load Balancer, Reverse Proxy and Service Mesh
| Tool | Main job | Works at | Typical position |
|---|---|---|---|
| Load balancer | Spreads traffic across server instances | Network or HTTP level | In front of the gateway or of each service |
| Reverse proxy | Forwards requests, handles HTTPS, serves static files | HTTP level | At the edge of the system |
| API gateway | Routing plus API concerns: security, rate limits, aggregation, transformation | API level | Between external clients and services (north–south traffic) |
| Service mesh | Secure, observable traffic between services: mTLS, retries, metrics | Service-to-service level | Inside the cluster (east–west traffic) |
These tools work together rather than replace each other. A typical setup is: cloud load balancer → API gateway → services, with an optional service mesh such as Istio or Linkerd handling traffic between services.
15. Popular API Gateway Solutions
| Solution | Type | Highlights |
|---|---|---|
| Spring Cloud Gateway | Open source, Java | Built on Spring Boot; routes in YAML or Java; natural fit for Spring teams |
| Kong | Open source and commercial | High-performance gateway built on NGINX, with a large plugin ecosystem |
| AWS API Gateway | Fully managed | Serverless, integrates with Lambda, IAM and other AWS services |
| Azure API Management | Fully managed | Complete API management from Microsoft, including a developer portal |
| Google Cloud API Gateway | Fully managed | Serverless gateway for Google Cloud back ends |
| Apigee | Managed platform | Full API management from Google Cloud: analytics, monetization, policies |
| Traefik | Open source | Modern reverse proxy and load balancer with automatic Kubernetes and Docker discovery |
| NGINX | Open source and commercial | Fast, widely used reverse proxy that can act as a simple gateway |
| Envoy | Open source | High-performance proxy; the base for many gateways and service meshes |
How to choose: a Java and Spring team that wants full control often picks Spring Cloud Gateway. Teams fully on one cloud often pick that cloud’s managed gateway to avoid running servers. Organizations that publish APIs to many external partners often need a full API management platform such as Apigee, Kong or Azure API Management.
16. Benefits and Drawbacks
| Benefits | Drawbacks |
|---|---|
| Clients use one address and make fewer calls | Another component to build, run and monitor |
| Security, rate limiting and logging live in one place | Can become a single point of failure if not made highly available |
| Internal services are hidden and can change freely | Adds a network hop, and so a little latency |
| Easier versioning, canary releases and migrations | Risk of becoming a bottleneck if too much logic is added |
| Responses can be tailored and aggregated for clients | One shared gateway can slow teams down if every change needs the gateway team |
Avoid the single point of failure by running several gateway instances behind a load balancer, across availability zones. Keep the extra latency small by keeping filters light and avoiding blocking calls.
17. Best Practices
- Keep the gateway thin. Routing and cross-cutting concerns only; business logic belongs in services.
- Run several instances behind a load balancer so the gateway is never a single point of failure.
- Set timeouts on every route and use circuit breakers with fallbacks for important services.
- Validate tokens at the gateway, and still authorize in the services (defense in depth).
- Apply rate limits per client or per user, and return
429with a clear message. - Add correlation IDs and central logging, so a request can be followed across services.
- Use service discovery (
lb://) instead of hard-coded addresses. - Version your APIs, for example
/api/v1/..., and use the gateway to route versions. - Keep internal services private: only the gateway should be reachable from the internet.
- Monitor the gateway closely: latency, error rates and rate-limit rejections per route.
18. Common Mistakes to Avoid
| Mistake | What goes wrong | Better approach |
|---|---|---|
| Putting business logic in the gateway | The gateway becomes a new monolith that every team must change | Keep business rules in services; use BFFs for client-specific shaping |
| Running only one instance | One crash takes the whole system offline | Run several instances behind a load balancer |
| No timeouts | One slow service exhausts the gateway and every route slows down | Set connect and response timeouts, plus circuit breakers |
| Trusting the gateway alone for security | Internal callers or a route mistake bypass all checks | Also validate and authorize in each service |
| Blocking calls in reactive filters | The gateway’s few threads stall and throughput collapses | Use non-blocking clients such as WebClient |
| Retrying non-idempotent requests | A retried POST can create duplicate orders or payments | Retry only safe methods, or use idempotency keys |
| Services still reachable from outside | Attackers skip the gateway entirely | Expose only the gateway; keep services on a private network |
19. Summary
The API gateway is the front door of a microservices architecture. It gives clients a single entry point, routes each request to the right service, and handles cross-cutting concerns such as authentication, rate limiting, caching, logging and response aggregation in one place. With Spring Cloud Gateway you can define routes in YAML or Java, add filters, secure APIs with JWT, limit traffic with Redis, and protect the system with timeouts and circuit breakers. Keep the gateway thin and highly available, combine it with backends for frontends when clients differ a lot, and your microservices stay independent while the outside world sees one clean, secure and scalable API. Continue with the Circuit Breaker Pattern to learn more about keeping services resilient.
20. Frequently Asked Questions (FAQ)
Q1. Do I always need an API gateway for microservices?
No. A gateway is most useful when you have several services, external clients such as mobile apps or partners, and shared concerns such as security and rate limiting.
With only two or three services used by one internal web app, a simple reverse proxy or even direct calls may be enough. Add a gateway when the problems it solves actually appear, because it is one more component to run.
Q2. Isn't the API gateway a single point of failure?
It can be, if you run only one instance. In practice, you avoid this by:
- Running several gateway instances behind a load balancer.
- Spreading them across availability zones.
- Keeping the gateway stateless, so any instance can handle any request.
- Using health checks so broken instances are removed automatically.
Managed cloud gateways handle this for you.
Q3. Does an API gateway make requests slower?
It adds one extra network hop, usually a few milliseconds inside a data center. For most applications this is small compared with the time saved elsewhere:
- Clients make one call instead of many over slow internet connections.
- The gateway can cache frequent responses.
- Calls between the gateway and services use the fast internal network.
Keep filters light and non-blocking, and the overhead stays negligible.
Q4. What is the difference between an API gateway and a load balancer?
A load balancer spreads traffic across identical server instances. It does not understand your API.
An API gateway understands requests at the API level: it routes /orders and /products to different services, checks tokens, applies rate limits and can combine responses. Gateways often use load balancing internally, and a load balancer often sits in front of the gateway instances.
Q5. Should the gateway or the services check user permissions?
Both, at different levels:
- The gateway checks that the request carries a valid token and may apply broad rules, such as “only admins can call
/api/admin/**”. - Each service applies detailed rules that need its own data, such as “customers may only see their own orders”.
This layered approach, called defense in depth, keeps you safe even if a route is misconfigured or an internal system calls a service directly.
Q6. What is the difference between Spring Cloud Gateway and Netflix Zuul?
Zuul 1 was Netflix’s gateway and was popular with early Spring Cloud. It used a blocking, thread-per-request model and is no longer part of Spring Cloud.
Spring Cloud Gateway is its replacement in the Spring world. It is actively maintained, built on modern Spring, and comes in a reactive (WebFlux) edition for high concurrency and a servlet (Web MVC) edition for teams that prefer the classic blocking style. New projects should use Spring Cloud Gateway.
Q7. Can I use Spring Cloud Gateway without WebFlux?
Yes. Besides the reactive edition used in this guide, there is a Web MVC edition built on the classic servlet stack:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway-server-webmvc</artifactId>
</dependency>
It is a good fit if your team does not use reactive programming. Its routes are configured in a similar way, but its filters and Java APIs differ, so pick one edition per gateway.
Q8. How does the gateway find services that scale up and down?
Through service discovery. Services register themselves, and the gateway looks them up by name:
- With Eureka or Consul, add the discovery client and use
uri: lb://order-service. - On Kubernetes, route to the service’s DNS name, such as
http://order-service:8080; Kubernetes balances across pods.
Either way, you never hard-code individual server addresses.
Q9. How do I version APIs with a gateway?
The gateway is a natural place to route API versions to different services or deployments:
routes:
- id: orders-v1
uri: lb://order-service
predicates:
- Path=/api/v1/orders/**
- id: orders-v2
uri: lb://order-service-v2
predicates:
- Path=/api/v2/orders/**
Old clients keep using /api/v1 while new clients move to /api/v2. Header-based versioning works the same way with a Header predicate.
Q10. Should I cache responses at the gateway?
Caching at the gateway works well for public, rarely changing data, such as product catalogs or configuration. It reduces load on services and speeds up responses.
Be careful with:
- Personal data: never serve one user’s cached response to another.
- Freshness: set short expiry times for data that changes, such as stock levels.
Often the simplest approach is to send proper Cache-Control headers so browsers and CDNs cache for you.
Q11. How do I handle CORS at the gateway?
Configure CORS once at the gateway instead of in every service:
spring:
cloud:
gateway:
server:
webflux:
globalcors:
cors-configurations:
'[/api/**]':
allowed-origins: "https://shop.example.com"
allowed-methods: GET,POST,PUT,DELETE
allowed-headers: Authorization,Content-Type
Then make sure the services do not add their own CORS headers as well, because duplicate headers make browsers reject the response.
Q12. What is the difference between an API gateway and a service mesh?
- An API gateway handles north–south traffic: requests coming from outside clients into the system.
- A service mesh, such as Istio or Linkerd, handles east–west traffic: calls between services inside the system, adding encryption (mTLS), retries and metrics through sidecar proxies.
Large systems often use both: a gateway at the edge and a mesh inside.
Q13. Can the gateway retry failed requests?
Yes, with the Retry filter, but use it carefully:
filters:
- name: Retry
args:
retries: 3
methods: GET
statuses: BAD_GATEWAY,SERVICE_UNAVAILABLE
Only retry safe, idempotent requests such as GET. Retrying a POST that creates an order could create it twice. Also keep retries few, or they can multiply load on a service that is already struggling.
Q14. How do I monitor an API gateway?
Spring Cloud Gateway works with Spring Boot Actuator and Micrometer. Watch at least:
- Request rate, error rate and latency per route.
- Rate-limit rejections (
429responses). - Circuit breaker states: which circuits are open.
- Gateway instance health: CPU, memory and connections.
Combine metrics with correlation IDs and distributed tracing to follow a slow request through every service.
Q15. Can one gateway serve both REST and WebSocket traffic?
Yes. Spring Cloud Gateway can route WebSocket connections with ws:// or wss:// URIs, or lb:ws:// with service discovery:
- id: notifications
uri: lb:ws://notification-service
predicates:
- Path=/ws/notifications/**
Remember that WebSocket connections stay open for a long time, so plan gateway capacity and load balancer timeouts accordingly.
Q16. Where should request validation happen?
Split it sensibly:
- The gateway can reject obviously bad requests early: missing tokens, oversized bodies, unknown paths or wrong content types.
- The service validates the business content: required fields, valid quantities, existing product IDs.
Do not move detailed business validation into the gateway, or every rule change will need a gateway release.
Q17. How many gateways should a system have?
Start with one logical gateway, run as several instances. Consider more when:
- Clients differ a lot, such as mobile, web and partners: use backends for frontends.
- Internal and external APIs need very different security: use separate external and internal gateways.
- One gateway team becomes a bottleneck for many product teams.
Avoid creating a gateway per service; that only adds hops without benefits.
Q18. What does "thin gateway" mean?
A thin gateway does only infrastructure work: routing, security checks, rate limiting, logging and simple transformations. It contains no business rules, such as discount calculations or order validation.
A thick gateway full of business logic becomes a new monolith: every feature needs a gateway change, and one bug can break all services. Keeping it thin preserves the independence that microservices are meant to give you.
Q19. How do I test routes in Spring Cloud Gateway?
Start the gateway in a test and point routes at a mock server, then call it with WebTestClient:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureWebTestClient
class GatewayRoutesTest {
@Autowired
private WebTestClient client;
@Test
void productRequestsAreRoutedAndPrefixIsStripped() {
client.get().uri("/api/products/7")
.exchange()
.expectStatus().isOk();
}
@Test
void ordersRequireAToken() {
client.get().uri("/api/orders/1")
.exchange()
.expectStatus().isUnauthorized();
}
}
Tools such as WireMock can stand in for the backend services, so you can check paths, headers and fallbacks without starting real services.
Q20. Is an API gateway the same as API management?
Not quite. An API gateway is the runtime component that handles traffic. API management is a broader platform that usually includes a gateway, plus:
- A developer portal with documentation and sign-up for API keys.
- Analytics on who uses which API and how much.
- Monetization, plans and quotas for paying customers.
- Life-cycle management for designing, publishing and retiring APIs.
Apigee, Azure API Management and Kong’s enterprise offering are API management platforms; Spring Cloud Gateway is a gateway.