00:00

Microservices API Gateway Pattern

Last updated:

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 and response aggregation.

1. What Is an API Gateway?

An API gateway is a server that acts as the single entry point for all client requests in microservices. 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.

An API gateway can also handle cross-cutting concerns such as authentication, authorization, rate limiting, request routing, and logging, reducing the need for each service to implement these features independently.

API Gateway architecture Web, mobile and partner clients send all requests over HTTPS to one API gateway. The gateway handles authentication, rate limiting, routing, load balancing, and logging and metrics, then forwards each request by its path to the right microservice: /api/users to the user service, /api/products to the product service, /api/orders to the order service and /api/payments to the payment service. Each service has its own database: users_db, products_db, orders_db and payments_db. CLIENTS API GATEWAY MICROSERVICES Web Appbrowser Mobile AppiOS / Android Partner SystemB2B integration HTTPS Single entry point api.example.com Authentication Rate limiting Routing Load balancing Logging & metrics /api/users User Service users_db /api/products Product Service products_db /api/orders Order Service orders_db /api/payments Payment Service payments_db
Figure 1: Clients send every request to the API gateway, which applies shared policies once and routes each request by its path to the right microservice.

2. How a Request Flows Through the Gateway

Every clients request passes through the same steps inside an API gateway:

  1. Receive the request from the client, for example GET /api/orders/42.
  2. Authenticate the caller by checking their token.
  3. Apply policies such as rate limits.
  4. Route: find the matching rule and pick the target service, here the order service.
  5. Transform the request if needed, for example add headers or rewrite the path.
  6. Forward the request to a healthy instance of the service, with a timeout.
  7. Return the response to the client, possibly transformed, and record logs and metrics.

3. Core Responsibilities of an API Gateway

An API Gateway handles the following important responsibilities in a microservices architecture.

#ResponsibilityWhat it means
1Request routingSends each request to the correct service based on the URL path, HTTP method or headers
2API composition and aggregationCombines data from several services into one response, so the client makes one call instead of many
3Authentication and authorizationChecks API keys, JSON Web Tokens (JWT) or other credentials before requests reach the services
4Rate limiting and throttlingProtects services from being overwhelmed by too many requests from one client
5CachingStores frequent responses to reduce load and improve response times
6Load balancingSpreads requests across several instances of a service
7Logging and monitoringRecords every request and collects metrics, giving one place to watch API health
8Transformation and protocol translationConverts 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.

4. 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.

Scenario 1: without an API gateway

  1. Call the user service to check the login.
  2. Call the order service for the list of orders.
  3. Call the product service once for each product in those orders.
  4. Call the inventory service for stock levels.
  5. 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.

Scenario 2: with an API gateway

The app makes one request: GET /my/order-history. Behind the scenes:

  1. The mobile app sends the request with its authentication token.
  2. The gateway validates the token.
  3. The gateway calls the order, product and inventory services over the fast internal network, in parallel where possible.
  4. It combines the responses into a single JSON object.
  5. 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.

5. 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.

5.1 Dependency

pom.xml
<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.

5.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.

application.yml
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 predicateMatches when
Path=/api/orders/**The URL path matches the pattern
Method=GET,POSTThe HTTP method is one of those listed
Header=X-Api-Version, 2A header has a given value
Host=**.shop.example.comThe host name matches
Query=categoryA query parameter is present
Weight=orders, 90Used to send a share of traffic, for example 90%, to one version

Using service discovery: In production, services usually register with a discovery server such as Eureka, or run in Kubernetes. Write 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.

6. Defining Routes in Java

The same routes can be written in Java, which is handy when you need logic or want compile-time checking:

GatewayRoutesConfig.java
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.

The order route’s circuitBreaker filter needs the spring-cloud-starter-circuitbreaker-reactor-resilience4j dependency, and the gateway must handle /fallback/orders, for example with a small controller that returns HTTP 503 and a friendly message. See the Circuit Breaker Pattern guide for details.

7. Filters: Changing Requests and Responses

7.1 Built-in filters

FilterWhat it does
StripPrefix=1Removes the first part of the path, such as /api
RewritePath=/api/(?<rest>.*), /${rest}Rewrites the path with a regular expression
AddRequestHeader=X-Source, gatewayAdds a header before forwarding
AddResponseHeader=X-Served-By, shop-gatewayAdds a header to the response
Retry=3Retries failed requests, best used for safe GET calls
RequestRateLimiterLimits requests per client (see the Rate Limiting Pattern)
CircuitBreakerStops calling a failing service and uses a fallback (see the Circuit Breaker Pattern)

7.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:

CorrelationIdFilter.java
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.

8. 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:

pom.xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
application.yml
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://login.example.com/realms/shop
GatewaySecurityConfig.java
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”.

Defense in depth: The gateway is the first check, not the only one. Services should still validate tokens and apply their own authorization rules, so that a misconfigured route or an internal caller cannot bypass security.

9. Aggregating Responses from Multiple Services

Routing forwards one request to one service. However, when a client screen requires data from multiple services, the API Gateway can call those services in parallel and combine their responses into a single result.

DashboardController.java
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.

10. API Gateway vs Load Balancer and Reverse Proxy

ToolMain jobWorks atTypical position
Load balancerSpreads traffic across server instancesNetwork or HTTP levelIn front of the gateway or of each service
Reverse proxyForwards requests, handles HTTPS, serves static filesHTTP levelAt the edge of the system
API gatewayRouting plus API concerns: security, rate limits, aggregation, transformationAPI levelBetween external clients and services (north–south traffic)

These tools work together rather than replace each other. A typical setup is: cloud load balancer → API gateway → services.

11. Popular API Gateway Solutions

SolutionTypeHighlights
Spring Cloud GatewayOpen source, JavaBuilt on Spring Boot; routes in YAML or Java; natural fit for Spring teams
KongOpen source and commercialHigh-performance gateway built on NGINX, with a large plugin ecosystem
AWS API GatewayFully managedServerless, integrates with Lambda, IAM and other AWS services
Azure API ManagementFully managedComplete API management from Microsoft, including a developer portal
Google Cloud API GatewayFully managedServerless gateway for Google Cloud back ends
ApigeeManaged platformFull API management from Google Cloud: analytics, monetization, policies
TraefikOpen sourceModern reverse proxy and load balancer with automatic Kubernetes and Docker discovery
NGINXOpen source and commercialFast, widely used reverse proxy that can act as a simple gateway
EnvoyOpen sourceHigh-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.

12. Benefits and Drawbacks of an API Gateway

12.1 Benefits

  • Clients use one address and make fewer calls.
  • Security, rate limiting and logging live in one place.
  • Internal services are hidden and can change freely.
  • Easier versioning, canary releases and migrations.
  • Responses can be tailored and aggregated for clients.

12.2 Drawbacks

  • Another component to build, run and monitor.
  • Can become a single point of failure if not made highly available.
  • Adds a network hop, and so a little latency.
  • Risk of becoming a bottleneck if too much logic is added.
  • 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.

13. Best Practices

  1. Keep the gateway thin. Routing and cross-cutting concerns only; business logic belongs in services.
  2. Run several instances behind a load balancer so the gateway is never a single point of failure.
  3. Set timeouts on every route and use circuit breakers with fallbacks for important services.
  4. Validate tokens at the gateway, and still authorize in the services (defense in depth).
  5. Apply rate limits per client or per user, and return 429 with a clear message.
  6. Add correlation IDs and central logging, so a request can be followed across services.
  7. Use service discovery (lb://) instead of hard-coded addresses.
  8. Version your APIs, for example /api/v1/..., and use the gateway to route versions.
  9. Keep internal services private: only the gateway should be reachable from the internet.
  10. Monitor the gateway closely: latency, error rates and rate-limit rejections per route.

14. Common Mistakes to Avoid

MistakeWhat goes wrongBetter approach
Putting business logic in the gatewayThe gateway becomes a new monolith that every team must changeKeep business rules in services; use BFFs for client-specific shaping
Running only one instanceOne crash takes the whole system offlineRun several instances behind a load balancer
No timeoutsOne slow service exhausts the gateway and every route slows downSet connect and response timeouts, plus circuit breakers
Trusting the gateway alone for securityInternal callers or a route mistake bypass all checksAlso validate and authorize in each service
Blocking calls in reactive filtersThe gateway’s few threads stall and throughput collapsesUse non-blocking clients such as WebClient
Retrying non-idempotent requestsA retried POST can create duplicate orders or paymentsRetry only safe methods, or use idempotency keys
Services still reachable from outsideAttackers skip the gateway entirelyExpose only the gateway; keep services on a private network

15. 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.