P2 — Spring Boot & Spring Cloud Gateway BFF Stack
P2 — Spring Boot & Spring Cloud Gateway BFF Stack
Research Question
What is the correct Spring Boot + Spring Cloud Gateway technology stack for building a production-grade BFF, and how do the reactive programming primitives (Project Reactor) enable the aggregation and fan-out patterns a BFF needs?
Why This Matters
A BFF must aggregate calls to multiple downstream services, enforce per-client contracts, and handle high concurrency with low latency. Choosing the wrong stack (blocking Servlet vs reactive WebFlux) or wrong versions (Spring Boot ↔ Spring Cloud mismatch) causes either correctness failures or production outages. This note pins down every dependency version and architectural decision for the implementation phases.
Background
Spring Cloud Gateway (SCG) replaced the older Netflix Zuul as the recommended API gateway / BFF foundation in the Spring ecosystem. It is built on Spring WebFlux (Project Reactor), giving it non-blocking I/O end-to-end. Phase 1 established why the BFF pattern is needed; this phase grounds the implementation in the concrete Spring stack.
Cross-reference: BFF-Pattern · API-Gateway-Pattern
Findings
STACK-01: Why Spring Cloud Gateway is the Natural BFF Foundation
Confidence: HIGH Source: Spring Cloud Gateway official docs (spring.io/projects/spring-cloud-gateway), Spring Cloud 2024.0 release notes
Spring Cloud Gateway (SCG) is the dominant Spring-ecosystem choice for a BFF layer for the following reasons:
-
Built on Spring WebFlux — SCG runs entirely on the reactive Netty server, giving it an event-loop model and non-blocking I/O. Every request is handled asynchronously; there is no thread-per-request overhead.
-
Route/Predicate/Filter model maps directly to BFF responsibilities — A BFF must route different clients to different downstream contracts. SCG's model is:
- Route — the top-level unit: id + URI + list of predicates + list of filters.
- Predicate — decides whether a request matches a route (Path, Host, Header, Method, Query, Cookie, etc.).
- Filter — transforms the request/response (add/remove headers, rewrite paths, add auth tokens, circuit-break, rate-limit, etc.).
-
Deep Spring Security integration —
SecurityWebFilterChainplugs into the reactive filter chain natively. Token relay, OAuth2 resource server, and session management all work without bridging layers. -
Spring Cloud ecosystem — SCG integrates with:
- Spring Cloud Config — externalised route config, hot reload via
/actuator/gateway/refresh. - Spring Cloud LoadBalancer — client-side load balancing for downstream service calls (replaces deprecated Ribbon).
- Spring Cloud CircuitBreaker (Resilience4j) — per-route circuit breakers as a Gateway filter.
- Spring Cloud Discovery (Eureka, Consul) —
lb://service-nameURIs resolved from the service registry.
- Spring Cloud Config — externalised route config, hot reload via
STACK-02: Reactive vs Servlet Stack — Which to Use for a BFF and Why
Confidence: HIGH Source: Spring Framework reference docs, Project Reactor docs, Baeldung Spring WebFlux guide
The Core Problem with Servlet (Spring MVC) for BFF
A BFF's defining characteristic is fan-out aggregation: one client request triggers N calls to downstream services. Under the Servlet model:
| Problem | Detail |
|---|---|
| Thread-per-request | Each inbound request ties up a Tomcat/Undertow thread for the entire duration |
| Blocking I/O | RestTemplate blocks the thread during downstream HTTP calls |
| Fan-out amplification | Aggregating 3 downstream services means 3× thread starvation at load |
| Thread pool exhaustion | Under moderate concurrency, the default 200 Tomcat threads fill up fast |
WebFlux Advantages for BFF
| Advantage | Detail |
|---|---|
| Event loop | Netty uses a small fixed pool of I/O threads; request handling is callback-driven |
| Non-blocking I/O | WebClient never blocks; downstream calls are modelled as Mono/Flux |
| Fan-out is cheap | Mono.zip(callA, callB, callC) runs 3 calls in parallel, no extra threads needed |
| Backpressure | Reactive streams propagate demand signals, preventing downstream overload |
| SCG required | Spring Cloud Gateway only runs on WebFlux; you cannot use it with MVC |
When Servlet Might Still Be Acceptable
- The BFF is a thin pass-through with no aggregation and minimal concurrency.
- The team has no reactive experience and the project timeline is very short.
- All downstream services are synchronous JDBC-backed services (reactive wins less here).
- You opt for a different gateway product (e.g., Nginx + small MVC aggregator).
Recommendation: For any BFF that does true aggregation, use WebFlux. The complexity cost of learning Reactor is real but pays off at the first load test.
Comparison Table: Reactive vs Servlet for BFF
| Criterion | Spring MVC (Servlet) | Spring WebFlux (Reactive) |
|---|---|---|
| Concurrency model | Thread-per-request | Event loop + callbacks |
| I/O model | Blocking | Non-blocking |
| Downstream calls | RestTemplate (blocking) | WebClient (non-blocking) |
| Fan-out aggregation | Thread-intensive, risky | Native with Mono.zip |
| Spring Cloud Gateway | Not supported | Required |
| Learning curve | Low | Medium-High |
| Debugging / stack traces | Easy | Harder (reactor traces) |
| Memory per connection | ~1 MB stack per thread | Very low (shared event loop) |
| Recommended for BFF? | Only for simple pass-through | YES — for any real BFF |
STACK-03: Project Reactor Basics for BFF Development
Confidence: HIGH Source: Project Reactor reference docs (projectreactor.io/docs), Reactor Core 3.6.x
The Two Core Types
| Type | Semantics | BFF Use Case |
|---|---|---|
Mono<T> | 0 or 1 element, then complete or error | Single downstream service call, single aggregated response |
Flux<T> | 0 to N elements, then complete or error | Streaming responses, lists of items from a downstream service |
Key Operators for BFF Patterns
| Operator | What it does | BFF Use Case |
|---|---|---|
flatMap | Maps each element to a Publisher, subscribes concurrently | Call downstream service for each item in a list |
map | Synchronous transform | Map DTO to response model |
Mono.zip(m1, m2, ...) | Subscribe to N Monos in parallel, emit tuple when all complete | Aggregate N downstream responses |
zipWith | Zip two Publishers element-by-element | Combine two Monos sequentially inline |
mergeWith | Merge two Flux streams | Combine two streams of events |
switchIfEmpty | Fallback when upstream is empty | Default/cache response if downstream returns nothing |
onErrorResume | Catch error, return fallback Mono/Flux | Circuit-breaker fallback, graceful degradation |
onErrorReturn | Catch error, return a plain value | Return null/default when downstream fails |
timeout | Error if upstream doesn't emit within duration | Enforce SLA on downstream calls |
retry / retryWhen | Re-subscribe on error | Retry transient failures with backoff |
doOnNext / doOnError | Side effects (logging, metrics) | Structured logging, Micrometer metrics |
cache | Cache the result of a Mono | Avoid duplicate downstream calls |
subscribeOn | Switch scheduler for blocking work | Offload blocking DB/legacy calls to bounded elastic pool |
The Aggregation Pattern (Mono.zip)
// Calling two downstream services in parallel and merging
Mono<UserProfile> userMono = userService.getProfile(userId);
Mono<List<Order>> ordersMono = orderService.getRecentOrders(userId);
Mono<DashboardResponse> result = Mono.zip(userMono, ordersMono)
.map(tuple -> DashboardResponse.builder()
.user(tuple.getT1())
.orders(tuple.getT2())
.build());Key point: Mono.zip subscribes to both Monos simultaneously. Total latency ≈ max(userLatency, ordersLatency), not sum.
STACK-04: Spring Cloud Gateway Core Concepts
Confidence: HIGH Source: Spring Cloud Gateway reference docs, spring.io/projects/spring-cloud-gateway
Route
A Route is the fundamental building block of SCG. It consists of:
- id — unique string identifier
- uri — downstream target (
http://,lb://,ws://) - predicates — list of conditions that must all match
- filters — list of transformations applied to the request/response
Built-in Predicates
| Predicate | Example | Use |
|---|---|---|
Path | Path=/api/users/** | Route by URL path |
Method | Method=GET,POST | Route by HTTP method |
Header | Header=X-Client-Type, mobile | Route by header (regex) |
Query | Query=version, v2 | Route by query param |
Host | Host=**.mobile.example.com | Route by hostname |
Cookie | Cookie=session, .* | Route by cookie |
After / Before / Between | After=2024-01-01T00:00:00Z | Route by time window |
RemoteAddr | RemoteAddr=192.168.1.0/24 | Route by IP range |
Weight | Weight=group1, 80 | Weighted routing (canary) |
Built-in Filters
| Filter | What it Does |
|---|---|
AddRequestHeader | Add a header before forwarding to downstream |
AddResponseHeader | Add a header to the response |
RemoveRequestHeader | Strip a header (e.g., internal auth headers from client) |
RewritePath | Rewrite URL path using regex |
StripPrefix | Remove path prefix segments |
RedirectTo | Issue HTTP redirect |
CircuitBreaker | Resilience4j circuit breaker per route |
Retry | Retry downstream call on failure |
RequestRateLimiter | Redis-backed rate limiting per client |
RequestSize | Reject requests above a size limit |
SetStatus | Override response status code |
TokenRelay | Forward OAuth2 token to downstream (see Token-Relay-Pattern) |
SaveSession | Flush Spring Session before forwarding |
DedupeResponseHeader | Remove duplicate response headers |
GatewayFilter vs GlobalFilter
| Type | Scope | Implementation |
|---|---|---|
GatewayFilter | Applied to a specific route | Configured in route definition or via GatewayFilterFactory |
GlobalFilter | Applied to ALL routes | Implement GlobalFilter + Ordered |
GlobalFilter is the right abstraction for cross-cutting concerns: auth header injection, request ID propagation, logging, metrics.
ServerWebExchange — The Core Context Object
ServerWebExchange is the mutable context for each request/response cycle in SCG:
public interface ServerWebExchange {
ServerHttpRequest getRequest(); // immutable request
ServerHttpResponse getResponse(); // mutable response
Map<String, Object> getAttributes(); // mutable bag for filter-to-filter communication
Mono<WebSession> getSession();
<T> Mono<T> getPrincipal();
// mutate() returns a builder to create modified copies
ServerWebExchange.Builder mutate();
}To modify the request in a filter (e.g., add a header):
ServerHttpRequest mutatedRequest = exchange.getRequest().mutate()
.header("X-Downstream-Caller", "bff")
.build();
return chain.filter(exchange.mutate().request(mutatedRequest).build());STACK-05: Bootstrap a BFF Project from Scratch
Confidence: HIGH Source: Spring Initializr (start.spring.io), Spring Cloud releases page, Spring Boot 3.x release notes
Spring Initializr Dependency Selection
When creating a new project at start.spring.io:
| Dependency | Artifact | Purpose |
|---|---|---|
| Gateway | spring-cloud-starter-gateway | Spring Cloud Gateway (includes WebFlux + Netty) |
| Reactive Web | spring-boot-starter-webflux | Included transitively via gateway |
| Spring Security | spring-boot-starter-security | Authentication / authorisation |
| OAuth2 Client | spring-boot-starter-oauth2-client | OIDC login + token relay |
| Actuator | spring-boot-starter-actuator | Health, metrics, gateway management |
| Config Client | spring-cloud-starter-config | Externalised configuration |
| Circuit Breaker Resilience4j | spring-cloud-starter-circuitbreaker-reactor-resilience4j | Circuit breakers |
| Micrometer Prometheus | micrometer-registry-prometheus | Metrics export |
Do NOT add spring-boot-starter-web (Servlet). It conflicts with WebFlux and will cause startup failure.
Spring Boot ↔ Spring Cloud Version Matrix
| Spring Boot | Spring Cloud Release Train | Spring Cloud Version | Notes |
|---|---|---|---|
| 3.0.x | 2022.0.x (Kilburn) | 4.0.x | EOL |
| 3.1.x | 2022.0.x (Kilburn) | 4.0.x | EOL |
| 3.2.x | 2023.0.x (Leyton) | 4.1.x | Maintenance |
| 3.3.x | 2023.0.x (Leyton) | 4.1.x | Maintenance |
| 3.4.x | 2024.0.x (Moore) | 4.2.x | Current (2025) |
| 3.5.x (planned) | 2024.0.x (Moore) | 4.2.x | Upcoming |
For new projects in 2025/2026: use Spring Boot 3.4.x + Spring Cloud 2024.0.x (Moore). This is the current supported combination.
Spring Cloud Gateway version in 2024.0.x train: 4.2.x
Project Reactor versions (transitive, do not declare directly):
- Reactor Core: 3.6.x (included via
spring-boot-starter-webflux) - Reactor Netty: 1.2.x (included via SCG)
Code Examples
Example 1: Minimal build.gradle (Kotlin DSL)
import org.springframework.boot.gradle.tasks.bundling.BootJar
plugins {
id("org.springframework.boot") version "3.4.3"
id("io.spring.dependency-management") version "1.1.7"
kotlin("jvm") version "2.0.21"
kotlin("plugin.spring") version "2.0.21"
}
group = "com.example"
version = "0.0.1-SNAPSHOT"
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
repositories {
mavenCentral()
}
extra["springCloudVersion"] = "2024.0.1"
dependencyManagement {
imports {
mavenBom("org.springframework.cloud:spring-cloud-dependencies:${property("springCloudVersion")}")
}
}
dependencies {
// Spring Cloud Gateway (pulls in spring-boot-starter-webflux + reactor-netty)
implementation("org.springframework.cloud:spring-cloud-starter-gateway")
// Security — OAuth2 client for OIDC login + token relay
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.springframework.boot:spring-boot-starter-oauth2-client")
// Actuator — health + gateway management endpoints
implementation("org.springframework.boot:spring-boot-starter-actuator")
// Circuit breaker via Resilience4j (reactive)
implementation("org.springframework.cloud:spring-cloud-starter-circuitbreaker-reactor-resilience4j")
// Metrics
implementation("io.micrometer:micrometer-registry-prometheus")
// Redis for rate limiting (optional, comment out if not using RequestRateLimiter)
// implementation("org.springframework.boot:spring-boot-starter-data-redis-reactive")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("io.projectreactor:reactor-test")
}
tasks.withType<Test> {
useJUnitPlatform()
}Version pins (2025/2026):
- Spring Boot:
3.4.3- Spring Cloud BOM:
2024.0.1- Java toolchain:
21- These are the current stable releases as of mid-2025.
Example 2: Minimal application.yml for Spring Cloud Gateway BFF
spring:
application:
name: bff-gateway
cloud:
gateway:
# Global CORS configuration
globalcors:
corsConfigurations:
'[/**]':
allowedOrigins:
- "http://localhost:4200" # Angular dev server
- "https://app.example.com"
allowedMethods: [GET, POST, PUT, DELETE, OPTIONS]
allowedHeaders: ["*"]
allowCredentials: true
maxAge: 3600
# Route definitions
routes:
# User service route
- id: user-service
uri: lb://user-service # load-balanced via Spring Cloud LoadBalancer
predicates:
- Path=/api/users/**
filters:
- StripPrefix=1 # removes /api before forwarding
- TokenRelay= # forwards OAuth2 access token to downstream
- name: CircuitBreaker
args:
name: userServiceCB
fallbackUri: forward:/fallback/users
- name: Retry
args:
retries: 3
statuses: BAD_GATEWAY,SERVICE_UNAVAILABLE
# Order service route
- id: order-service
uri: lb://order-service
predicates:
- Path=/api/orders/**
filters:
- StripPrefix=1
- TokenRelay=
- name: CircuitBreaker
args:
name: orderServiceCB
fallbackUri: forward:/fallback/orders
# BFF aggregation endpoint — handled by a local controller
- id: dashboard-aggregate
uri: http://localhost:${server.port} # self-referencing for local controllers
predicates:
- Path=/api/dashboard/**
filters:
- TokenRelay=
# OAuth2 resource server — validate JWTs from upstream IdP
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://idp.example.com
server:
port: 8080
management:
endpoints:
web:
exposure:
include: health, info, prometheus, gateway
endpoint:
health:
show-details: always
gateway:
enabled: true # exposes /actuator/gateway/routes etc.
health:
circuitbreakers:
enabled: true
logging:
level:
org.springframework.cloud.gateway: DEBUG # remove in production
reactor.netty: INFOExample 3: Custom GlobalFilter Skeleton
package com.example.bff.filter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.UUID;
/**
* GlobalFilter that:
* - PRE: injects a correlation ID into the request (X-Correlation-Id header)
* - POST: echoes the correlation ID back in the response header
*
* Runs before all route-specific filters (order = -1).
*/
@Component
public class CorrelationIdGlobalFilter implements GlobalFilter, Ordered {
private static final Logger log = LoggerFactory.getLogger(CorrelationIdGlobalFilter.class);
private static final String CORRELATION_ID_HEADER = "X-Correlation-Id";
@Override
public int getOrder() {
// Negative order = runs before most other filters.
// Use Ordered.HIGHEST_PRECEDENCE for absolute first position.
return -1;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// --- PRE-processing ---
String correlationId = exchange.getRequest().getHeaders()
.getFirst(CORRELATION_ID_HEADER);
if (correlationId == null || correlationId.isBlank()) {
correlationId = UUID.randomUUID().toString();
}
log.debug("Request [{}] {} {}",
correlationId,
exchange.getRequest().getMethod(),
exchange.getRequest().getURI().getPath());
// Mutate request to add correlation ID (request is immutable, must use mutate())
final String finalCorrelationId = correlationId;
ServerHttpRequest mutatedRequest = exchange.getRequest().mutate()
.header(CORRELATION_ID_HEADER, finalCorrelationId)
.build();
// Store in exchange attributes for downstream filters to read
exchange.getAttributes().put(CORRELATION_ID_HEADER, finalCorrelationId);
// Build a mutated exchange with the new request
ServerWebExchange mutatedExchange = exchange.mutate()
.request(mutatedRequest)
.build();
// --- Continue filter chain, then POST-processing ---
return chain.filter(mutatedExchange)
.then(Mono.fromRunnable(() -> {
// POST: runs after the downstream response is received
ServerHttpResponse response = mutatedExchange.getResponse();
response.getHeaders().add(CORRELATION_ID_HEADER, finalCorrelationId);
log.debug("Response [{}] status={}",
finalCorrelationId,
response.getStatusCode());
}));
}
}Key patterns demonstrated:
Orderedinterface controls filter execution order.exchange.getRequest().mutate()creates an immutable copy with modified headers.exchange.mutate().request(...).build()replaces the request in the exchange.chain.filter(...).then(Mono.fromRunnable(...))is the standard pre/post hook pattern.
Example 4: Mono.zip() for Parallel Service Calls
package com.example.bff.aggregation;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import java.time.Duration;
import java.util.List;
/**
* BFF aggregation service: calls User Service and Order Service in parallel,
* merges the results into a single DashboardResponse.
*/
@Service
public class DashboardAggregationService {
private final WebClient userServiceClient;
private final WebClient orderServiceClient;
public DashboardAggregationService(WebClient.Builder webClientBuilder) {
// In production, use Spring Cloud LoadBalancer: "lb://user-service"
this.userServiceClient = webClientBuilder
.baseUrl("http://user-service")
.build();
this.orderServiceClient = webClientBuilder
.baseUrl("http://order-service")
.build();
}
/**
* Aggregates user profile + recent orders into a single response.
* Both downstream calls happen in PARALLEL; total latency ≈ max(t1, t2).
*/
public Mono<DashboardResponse> getDashboard(String userId, String bearerToken) {
Mono<UserProfile> userProfileMono = fetchUserProfile(userId, bearerToken)
.timeout(Duration.ofSeconds(3))
.onErrorResume(ex -> {
// graceful degradation: return a minimal profile on failure
return Mono.just(UserProfile.empty(userId));
});
Mono<List<Order>> recentOrdersMono = fetchRecentOrders(userId, bearerToken)
.timeout(Duration.ofSeconds(3))
.onErrorResume(ex -> {
// graceful degradation: return empty order list on failure
return Mono.just(List.of());
});
// Mono.zip subscribes to BOTH monos simultaneously.
// When BOTH complete, the combinator function is called with their results.
return Mono.zip(userProfileMono, recentOrdersMono)
.map(tuple -> DashboardResponse.builder()
.userId(userId)
.user(tuple.getT1()) // result of userProfileMono
.orders(tuple.getT2()) // result of recentOrdersMono
.build());
}
private Mono<UserProfile> fetchUserProfile(String userId, String token) {
return userServiceClient.get()
.uri("/users/{id}", userId)
.headers(h -> h.setBearerAuth(token))
.retrieve()
.bodyToMono(UserProfile.class);
}
private Mono<List<Order>> fetchRecentOrders(String userId, String token) {
return orderServiceClient.get()
.uri(uriBuilder -> uriBuilder
.path("/orders")
.queryParam("userId", userId)
.queryParam("limit", 10)
.build())
.headers(h -> h.setBearerAuth(token))
.retrieve()
.bodyToFlux(Order.class)
.collectList();
}
// DTO stubs (would be in separate files in production)
public record UserProfile(String id, String name, String email) {
public static UserProfile empty(String id) {
return new UserProfile(id, "Unknown", "");
}
}
public record Order(String orderId, String status, double total) {}
public record DashboardResponse(String userId, UserProfile user, List<Order> orders) {
public static Builder builder() { return new Builder(); }
public static class Builder {
private String userId;
private UserProfile user;
private List<Order> orders;
public Builder userId(String v) { this.userId = v; return this; }
public Builder user(UserProfile v) { this.user = v; return this; }
public Builder orders(List<Order> v) { this.orders = v; return this; }
public DashboardResponse build() {
return new DashboardResponse(userId, user, orders);
}
}
}
}Key patterns:
Mono.zip(m1, m2)— parallel subscription, tuple result..timeout(Duration.ofSeconds(3))— enforce SLA per call..onErrorResume(ex -> Mono.just(fallback))— graceful degradation; one failing service does not fail the whole dashboard.bodyToFlux(Order.class).collectList()— stream a list from downstream, collect intoMono<List<Order>>for zipping.
Contradictions & Open Questions
- Spring Cloud Gateway 4.2.x introduced
spring-cloud-starter-gateway-mvc(a servlet-based variant for Spring MVC). Is there a legitimate BFF use case for it? Initial analysis: no — it lacks the non-blocking properties needed for aggregation. - Kotlin coroutines can be used instead of raw Reactor operators. Is this worth the switch for teams already using Kotlin? (Phase 3 may answer this.)
- How does the
TokenRelayfilter interact with the reactive Security context when downstream calls are made manually viaWebClientinside a custom aggregation controller? See Token-Relay-Pattern.
Synthesis
Spring Cloud Gateway on the reactive stack (WebFlux + Project Reactor + Netty) is not merely "one option" — it is architecturally the correct foundation for a BFF:
- The non-blocking event loop eliminates thread-per-request overhead, which is the dominant scalability constraint when a BFF fans out to N downstream services.
Mono.zipis the single most important operator for BFF development: it parallelises N downstream calls and merges results with no extra threading cost.- The Route/Predicate/Filter model is a direct encoding of BFF responsibilities (routing by client type, transforming requests/responses, enforcing auth).
- The Spring Cloud ecosystem (Config, LoadBalancer, CircuitBreaker, Discovery) provides all the production-grade infrastructure a BFF needs without custom code.
The version matrix is clear: Spring Boot 3.4.x + Spring Cloud 2024.0.x is the current supported production combination as of 2025.
Action Items
- Create Request-Aggregation topic note (Phase 3)
- Create Token-Relay-Pattern topic note (Phase 4)
- Validate exact Spring Cloud Gateway 4.2.x release version against Maven Central
- Write integration test using
WebTestClient+ WireMock for the aggregation service - Benchmark reactive vs servlet BFF under 500 concurrent users
Sources
| Source | Type | Quality | Notes |
|---|---|---|---|
| spring.io/projects/spring-cloud-gateway | Official docs | HIGH | Route/Predicate/Filter model, built-in filters |
| projectreactor.io/docs/core/release/reference | Official docs | HIGH | Mono/Flux operators, marble diagrams |
| spring.io/blog/spring-cloud-2024-0-0 | Release notes | HIGH | Spring Cloud 2024.0.x (Moore) release |
| start.spring.io | Tooling | HIGH | Dependency selection and BOM coordinates |
| Spring Boot Reference Docs 3.4.x | Official docs | HIGH | Actuator, auto-configuration, property binding |
| Baeldung: Exploring the New Spring Cloud Gateway | Tutorial | MEDIUM | Practical route/filter examples |
| Josh Long @starbuxman blog / Spring Tips videos | Expert guide | MEDIUM | Reactive patterns, WebClient usage |
Links
Related Topics: BFF-Pattern · API-Gateway-Pattern · Spring-Cloud-Gateway · Project-Reactor · Reactive-Programming Forward References: Request-Aggregation · Token-Relay-Pattern MOC: BFF Architecture MOC