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:

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

  2. 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.).
  3. Deep Spring Security integrationSecurityWebFilterChain plugs into the reactive filter chain natively. Token relay, OAuth2 resource server, and session management all work without bridging layers.

  4. 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-name URIs resolved from the service registry.

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:

ProblemDetail
Thread-per-requestEach inbound request ties up a Tomcat/Undertow thread for the entire duration
Blocking I/ORestTemplate blocks the thread during downstream HTTP calls
Fan-out amplificationAggregating 3 downstream services means 3× thread starvation at load
Thread pool exhaustionUnder moderate concurrency, the default 200 Tomcat threads fill up fast

WebFlux Advantages for BFF

AdvantageDetail
Event loopNetty uses a small fixed pool of I/O threads; request handling is callback-driven
Non-blocking I/OWebClient never blocks; downstream calls are modelled as Mono/Flux
Fan-out is cheapMono.zip(callA, callB, callC) runs 3 calls in parallel, no extra threads needed
BackpressureReactive streams propagate demand signals, preventing downstream overload
SCG requiredSpring 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

CriterionSpring MVC (Servlet)Spring WebFlux (Reactive)
Concurrency modelThread-per-requestEvent loop + callbacks
I/O modelBlockingNon-blocking
Downstream callsRestTemplate (blocking)WebClient (non-blocking)
Fan-out aggregationThread-intensive, riskyNative with Mono.zip
Spring Cloud GatewayNot supportedRequired
Learning curveLowMedium-High
Debugging / stack tracesEasyHarder (reactor traces)
Memory per connection~1 MB stack per threadVery low (shared event loop)
Recommended for BFF?Only for simple pass-throughYES — 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

TypeSemanticsBFF Use Case
Mono<T>0 or 1 element, then complete or errorSingle downstream service call, single aggregated response
Flux<T>0 to N elements, then complete or errorStreaming responses, lists of items from a downstream service

Key Operators for BFF Patterns

OperatorWhat it doesBFF Use Case
flatMapMaps each element to a Publisher, subscribes concurrentlyCall downstream service for each item in a list
mapSynchronous transformMap DTO to response model
Mono.zip(m1, m2, ...)Subscribe to N Monos in parallel, emit tuple when all completeAggregate N downstream responses
zipWithZip two Publishers element-by-elementCombine two Monos sequentially inline
mergeWithMerge two Flux streamsCombine two streams of events
switchIfEmptyFallback when upstream is emptyDefault/cache response if downstream returns nothing
onErrorResumeCatch error, return fallback Mono/FluxCircuit-breaker fallback, graceful degradation
onErrorReturnCatch error, return a plain valueReturn null/default when downstream fails
timeoutError if upstream doesn't emit within durationEnforce SLA on downstream calls
retry / retryWhenRe-subscribe on errorRetry transient failures with backoff
doOnNext / doOnErrorSide effects (logging, metrics)Structured logging, Micrometer metrics
cacheCache the result of a MonoAvoid duplicate downstream calls
subscribeOnSwitch scheduler for blocking workOffload 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

PredicateExampleUse
PathPath=/api/users/**Route by URL path
MethodMethod=GET,POSTRoute by HTTP method
HeaderHeader=X-Client-Type, mobileRoute by header (regex)
QueryQuery=version, v2Route by query param
HostHost=**.mobile.example.comRoute by hostname
CookieCookie=session, .*Route by cookie
After / Before / BetweenAfter=2024-01-01T00:00:00ZRoute by time window
RemoteAddrRemoteAddr=192.168.1.0/24Route by IP range
WeightWeight=group1, 80Weighted routing (canary)

Built-in Filters

FilterWhat it Does
AddRequestHeaderAdd a header before forwarding to downstream
AddResponseHeaderAdd a header to the response
RemoveRequestHeaderStrip a header (e.g., internal auth headers from client)
RewritePathRewrite URL path using regex
StripPrefixRemove path prefix segments
RedirectToIssue HTTP redirect
CircuitBreakerResilience4j circuit breaker per route
RetryRetry downstream call on failure
RequestRateLimiterRedis-backed rate limiting per client
RequestSizeReject requests above a size limit
SetStatusOverride response status code
TokenRelayForward OAuth2 token to downstream (see Token-Relay-Pattern)
SaveSessionFlush Spring Session before forwarding
DedupeResponseHeaderRemove duplicate response headers

GatewayFilter vs GlobalFilter

TypeScopeImplementation
GatewayFilterApplied to a specific routeConfigured in route definition or via GatewayFilterFactory
GlobalFilterApplied to ALL routesImplement 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:

DependencyArtifactPurpose
Gatewayspring-cloud-starter-gatewaySpring Cloud Gateway (includes WebFlux + Netty)
Reactive Webspring-boot-starter-webfluxIncluded transitively via gateway
Spring Securityspring-boot-starter-securityAuthentication / authorisation
OAuth2 Clientspring-boot-starter-oauth2-clientOIDC login + token relay
Actuatorspring-boot-starter-actuatorHealth, metrics, gateway management
Config Clientspring-cloud-starter-configExternalised configuration
Circuit Breaker Resilience4jspring-cloud-starter-circuitbreaker-reactor-resilience4jCircuit breakers
Micrometer Prometheusmicrometer-registry-prometheusMetrics 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 BootSpring Cloud Release TrainSpring Cloud VersionNotes
3.0.x2022.0.x (Kilburn)4.0.xEOL
3.1.x2022.0.x (Kilburn)4.0.xEOL
3.2.x2023.0.x (Leyton)4.1.xMaintenance
3.3.x2023.0.x (Leyton)4.1.xMaintenance
3.4.x2024.0.x (Moore)4.2.xCurrent (2025)
3.5.x (planned)2024.0.x (Moore)4.2.xUpcoming

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

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

  • Ordered interface 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 into Mono<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 TokenRelay filter interact with the reactive Security context when downstream calls are made manually via WebClient inside 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:

  1. 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.
  2. Mono.zip is the single most important operator for BFF development: it parallelises N downstream calls and merges results with no extra threading cost.
  3. The Route/Predicate/Filter model is a direct encoding of BFF responsibilities (routing by client type, transforming requests/responses, enforcing auth).
  4. 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

SourceTypeQualityNotes
spring.io/projects/spring-cloud-gatewayOfficial docsHIGHRoute/Predicate/Filter model, built-in filters
projectreactor.io/docs/core/release/referenceOfficial docsHIGHMono/Flux operators, marble diagrams
spring.io/blog/spring-cloud-2024-0-0Release notesHIGHSpring Cloud 2024.0.x (Moore) release
start.spring.ioToolingHIGHDependency selection and BOM coordinates
Spring Boot Reference Docs 3.4.xOfficial docsHIGHActuator, auto-configuration, property binding
Baeldung: Exploring the New Spring Cloud GatewayTutorialMEDIUMPractical route/filter examples
Josh Long @starbuxman blog / Spring Tips videosExpert guideMEDIUMReactive patterns, WebClient usage

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