Understanding API Gateways in Spring Boot Microservices
When you're building a microservices system with Spring Boot, an API gateway is the single entry point that sits in front of all your services, routing incoming requests to the right backend, handling cross-cutting concerns like auth and rate limiting, and hiding your internal service topology from clients. In the Spring ecosystem, this is almost always implemented with Spring Cloud Gateway, a reactive, non-blocking library purpose-built for this job.
Without a gateway, every client — a mobile app, a frontend, a partner integration — would need to know the address of every individual microservice and reimplement things like authentication and retries on its own. That doesn't scale past two or three services. The gateway centralizes that logic once, in one deployable Spring Boot application, instead of duplicating it across dozens of clients.
Why Spring Boot Microservices Need a Gateway
A typical Spring Boot microservices architecture might have separate services for orders, users, payments, and inventory, each running on its own port, possibly scaled independently, possibly deployed on different nodes. Without a gateway:
- Clients must track service locations directly, which breaks the moment you scale or move a service.
- Authentication and authorization logic gets copy-pasted into every service.
- There's no single place to apply rate limiting, logging, or request tracing.
- Combining data from multiple services requires the client to make multiple round trips.
The gateway solves this by exposing one public URL. Internally it forwards /api/orders/ to the orders service, /api/users/ to the users service, and so on, using service discovery (often Eureka or Kubernetes DNS) instead of hardcoded IPs.
How Spring Cloud Gateway Fits In
Spring Cloud Gateway is built on Spring WebFlux and Project Reactor, so it's asynchronous by design — it can hold thousands of in-flight requests without blocking threads, which matters when it's proxying traffic for your entire system. Routes are defined either in YAML or programmatically as Java beans.
A basic route definition in application.yml looks like this:
spring:
cloud:
gateway:
routes:
- id: orders-service
uri: lb://ORDERS-SERVICE
predicates:
- Path=/api/orders/**
filters:
- StripPrefix=1
- id: users-service
uri: lb://USERS-SERVICE
predicates:
- Path=/api/users/**
filters:
- StripPrefix=1
Here, lb://ORDERS-SERVICE tells the gateway to resolve the actual host through client-side load balancing (via Eureka or another discovery client), so you never hardcode instance addresses. The StripPrefix=1 filter removes /api before forwarding, so the orders service just sees /orders/**.
You can achieve the same thing with a Java configuration if you prefer code over YAML:
@Bean
public RouteLocator customRoutes(RouteLocatorBuilder builder) {
return builder.routes()
.route("orders-service", r -> r.path("/api/orders/**")
.filters(f -> f.stripPrefix(1))
.uri("lb://ORDERS-SERVICE"))
.build();
}
What the Gateway Handles Beyond Routing
Routing is the baseline. In practice, a Spring Boot API gateway usually also handles:
- Authentication and authorization — validating JWTs or session tokens before a request ever reaches a downstream service, often via a
GlobalFilteror Spring Security integration. - Rate limiting — Spring Cloud Gateway ships a Redis-backed
RequestRateLimiterfilter out of the box. - Circuit breaking — pairing with Resilience4j so a failing downstream service doesn't cascade failures back to clients.
- Request/response logging and tracing — attaching correlation IDs and forwarding them through Spring Cloud Sleuth or Micrometer Tracing.
- Response aggregation — in some setups, combining calls to multiple services into a single client-facing response (more common with a Backend-for-Frontend pattern than a pure gateway).
A minimal custom filter that checks for a bearer token looks like this:
@Component
public class AuthFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String authHeader = exchange.getRequest().getHeaders().getFirst("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
}
This is the same pattern used at the infrastructure level by dedicated API management platforms — the gateway checks credentials once, at the edge, so every service behind it can trust the request without re-validating it.
Gateway vs. Load Balancer vs. Service Mesh
These terms get conflated often enough that it's worth being precise:
- A load balancer distributes traffic across instances of a single service. It doesn't understand routes to different services.
- An API gateway understands your API surface — paths, versions, auth — and routes across many different services, often applying business-level policies.
- A service mesh (like Istio or Linkerd) handles service-to-service traffic inside the cluster, with sidecar proxies, and is usually complementary to a gateway rather than a replacement for it. The gateway is north-south (client to cluster); the mesh is east-west (service to service).
In a typical Spring Boot setup, you'll have the gateway at the edge and possibly a mesh internally, but plenty of systems run fine with just the gateway.
Where an External API Fits Behind Your Gateway
Not every downstream your gateway routes to has to be a service you wrote. If one of your Spring Boot microservices needs to call an LLM — for a support-ticket classifier, a summarization endpoint, or an internal tool — it's common to put that call behind its own internal service rather than scattering API keys and HTTP clients across the codebase.
This is where a tool like SubToAPI is useful: it turns your existing Claude access into a clean HTTPS API with its own application keys (sub_live_...), streaming support, and usage metadata, so your microservice can call https://api.subtoapi.app/v1/messages the same way it would call any other internal REST dependency, and your gateway can apply the same auth and rate-limiting rules to that route as it does to everything else. Setup takes a few minutes — see the quickstart and the messages API reference if you want to wire it into a service behind your gateway. Streaming responses are documented separately at docs/streaming, and tool-use support at docs/tools.
Getting Started Practically
If you're adding a gateway to an existing Spring Boot microservices project:
- Add
spring-cloud-starter-gatewayas a dependency. - Register services with a discovery client (Eureka, Consul, or your platform's native discovery).
- Define routes in YAML for a quick start, move to Java
RouteLocatorbeans once you need conditional logic. - Add a global filter for authentication before opening the gateway to real traffic.
- Add rate limiting and circuit breaking once you have real load patterns to tune against.
Don't try to make the gateway do too much business logic — its job is routing, security, and cross-cutting concerns, not domain logic that belongs in your services.
Questions
Is Spring Cloud Gateway the same as an API gateway? Spring Cloud Gateway is a specific implementation — a library you add to a Spring Boot application to build an API gateway. "API gateway" is the architectural pattern; Spring Cloud Gateway is one way to implement it in the Java/Spring ecosystem.
Do I need service discovery to use an API gateway in Spring Boot? No, you can hardcode uri values pointing to fixed hosts for simple setups. Service discovery (via lb://SERVICE-NAME) is recommended once you have multiple instances or services that move between hosts.
Can Spring Cloud Gateway replace Nginx? For internal service routing within a Spring Boot microservices system, yes, it often replaces Nginx entirely. Many teams still keep Nginx or a cloud load balancer in front for TLS termination and static asset serving, with the gateway handling application-level routing behind it.