Published on

Spring Cloud Gateway with Spring Boot 4: Routing, Security, Resilience, and BFF Composition

Authors
  • avatar
    Name
    Maria
    Twitter

A Spring Cloud Gateway deployment is more than a collection of route definitions. At the edge of a microservice system, it becomes the place where external contracts meet internal topology, authentication meets service authorization, and client latency meets downstream failure.

That position makes the gateway valuable, but it also makes poor design decisions unusually expensive. Blocking work on the Netty event loop, retries on unsafe operations, unbounded response aggregation, or a mismatched Spring Boot and Spring Cloud release train can turn the edge into a system-wide bottleneck.

This guide builds a production-oriented gateway using Spring Boot 4, Spring Cloud Gateway Server WebFlux, Spring Security, Resilience4j, and reactive WebClient composition. It also explains where Java virtual threads are useful—and why they are not the default concurrency model for the reactive gateway itself.

TL;DR Keep proxying, authentication, routing, and lightweight policy enforcement in the gateway. Apply strict timeouts, retry only safe operations, and use circuit breakers for controlled degradation. Put complex client-specific orchestration in a clear BFF boundary rather than hiding it inside low-level gateway filters.

What an API Gateway Should Own

A gateway is a north-south traffic component. It handles requests entering the system from browsers, mobile applications, partner APIs, and other external consumers.

Good gateway responsibilities include:

  • route selection and path normalization;
  • TLS termination at the platform or gateway boundary;
  • authentication and coarse-grained authorization;
  • request size limits and rate limits;
  • correlation and trace context propagation;
  • edge-level timeouts, retries, and circuit breakers;
  • removal of untrusted or sensitive headers;
  • consistent error response envelopes;
  • simple protocol or contract adaptation.

A gateway should not become a second domain monolith.

Avoid placing these responsibilities at the edge:

  • database transactions for core business entities;
  • long-running workflows;
  • ownership of domain invariants;
  • large response joins across many services;
  • CPU-heavy report generation;
  • arbitrary third-party calls without explicit timeouts and isolation;
  • client-specific logic shared by only one frontend when a separate BFF would be clearer.

A useful test is ownership: if a rule must remain correct even when a request bypasses the public gateway through an internal channel, that rule belongs in the downstream service too.

API Gateway, BFF, and Service Mesh Are Different Boundaries

These components can coexist because they solve different problems.

ComponentPrimary trafficMain responsibility
API GatewayExternal to internalRouting, authentication, edge policy, public contracts
Backend for FrontendClient-specificAggregation and response shaping for one client experience
Service MeshService to serviceInternal traffic policy, mTLS, telemetry, retries, traffic shifting

A common request path is:

Client
  -> external load balancer
  -> API Gateway
  -> client-specific BFF
  -> internal services

A smaller system can combine the gateway and a limited BFF endpoint in one deployment. If aggregation becomes complex, independently scaled, or owned by a frontend team, split it into a dedicated service.

Use a Compatible Spring Release Train

Spring Cloud versions are tied to Spring Boot generations. Do not select a Spring Boot version and an unrelated Spring Cloud snapshot independently.

At the time of this revision:

  • Spring Boot 4.0.7 is a stable Spring Boot 4.0 release;
  • Spring Cloud 2025.1.2 supports Spring Boot 4.0.x;
  • Spring Cloud Gateway 5.0.2 is the stable Gateway line managed by that release train.

Use the Spring Cloud BOM so that Gateway, LoadBalancer, CircuitBreaker, and other Spring Cloud modules remain aligned.

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="
           http://maven.apache.org/POM/4.0.0
           https://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>4.0.7</version>
        <relativePath />
    </parent>

    <groupId>com.example</groupId>
    <artifactId>edge-gateway</artifactId>
    <version>1.0.0</version>

    <properties>
        <java.version>25</java.version>
        <spring-cloud.version>2025.1.2</spring-cloud.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway-server-webflux</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

The Gateway WebFlux starter already brings the reactive server foundation. Adding a second generic WebFlux starter is usually unnecessary.

Spring Cloud Gateway Server WebFlux runs on Reactor Netty. It is not a traditional Servlet WAR application, and synchronous Servlet assumptions should not be copied into custom filters.

Configure Routes Explicitly

A route contains:

  • an identifier;
  • a target URI;
  • predicates that decide whether the route matches;
  • filters that modify or protect the exchange.

Current Gateway configuration places WebFlux routes under spring.cloud.gateway.server.webflux.routes.

spring:
  application:
    name: edge-gateway

  cloud:
    gateway:
      server:
        webflux:
          routes:
            - id: user-service
              uri: lb://user-service
              predicates:
                - Path=/api/users/**
              filters:
                - StripPrefix=1
                - RemoveRequestHeader=X-Internal-User
                - name: CircuitBreaker
                  args:
                    name: userService
                    fallbackUri: forward:/fallback/users

            - id: order-service
              uri: lb://order-service
              predicates:
                - Path=/api/orders/**
              filters:
                - StripPrefix=1
                - RemoveRequestHeader=X-Internal-User
                - name: Retry
                  args:
                    retries: 2
                    methods: GET
                    statuses: BAD_GATEWAY,GATEWAY_TIMEOUT
                    backoff:
                      firstBackoff: 100ms
                      maxBackoff: 500ms
                      factor: 2
                      basedOnPreviousValue: false

    loadbalancer:
      retry:
        enabled: false

server:
  port: 8080
  shutdown: graceful

With StripPrefix=1, this request:

GET /api/users/42

is sent downstream as:

GET /users/42

Use one path convention consistently. Mixing RewritePath, controller mappings, ingress rewrites, and downstream context paths makes routing failures difficult to diagnose.

Service discovery is optional

The lb:// scheme uses Spring Cloud LoadBalancer and a DiscoveryClient implementation. Depending on the platform, the registry might be Eureka, Consul, or Kubernetes discovery.

For a small Docker Compose environment, a direct target is simpler:

uri: http://user-service:8081

For Kubernetes, a stable service DNS name is often enough:

uri: http://user-service.default.svc.cluster.local

Do not introduce a service registry only because an example uses one. Select the discovery mechanism that matches the deployment platform.

Configure Timeouts Before Adding Retries

A gateway request without a bounded timeout can hold resources while a downstream dependency remains unavailable.

Spring Cloud Gateway supports global and per-route HTTP timeouts.

spring:
  cloud:
    gateway:
      httpclient:
        connect-timeout: 1000
        response-timeout: 3s

The global connect timeout is expressed in milliseconds, while the global response timeout accepts a duration.

A route can override both through metadata:

spring:
  cloud:
    gateway:
      server:
        webflux:
          routes:
            - id: report-service
              uri: lb://report-service
              predicates:
                - Path=/api/reports/**
              metadata:
                connect-timeout: 500
                response-timeout: 8000

Timeout values should come from an explicit latency budget:

Client timeout
  > Gateway processing
  + Gateway-to-service timeout
  + fallback or retry allowance
  + network margin

If the gateway can retry twice with a three-second response timeout, the client timeout must account for the worst-case path—or the client will give up while the gateway continues working.

Retry Only When the Operation Is Safe

Retries can improve availability for transient failures, but they can also repeat side effects.

Safer retry candidates include:

  • GET and HEAD requests;
  • idempotent operations protected by an idempotency key;
  • connection failures before the downstream service receives the request;
  • specific 502 or 504 responses.

Avoid automatic edge retries for ordinary POST requests unless the downstream contract explicitly guarantees idempotency.

A payment request illustrates the danger:

Gateway sends POST /payments
Payment service commits the charge
Response is lost
Gateway retries
Payment is charged twice

A correct design gives the client request a stable idempotency key and requires the payment service to enforce uniqueness. The gateway retry is not a replacement for downstream idempotency.

Use Circuit Breakers for Controlled Degradation

The modern Gateway filter is CircuitBreaker, backed by Spring Cloud CircuitBreaker and commonly Resilience4j. Do not use the retired Hystrix filter.

spring:
  cloud:
    gateway:
      server:
        webflux:
          routes:
            - id: catalog-service
              uri: lb://catalog-service
              predicates:
                - Path=/api/catalog/**
              filters:
                - name: CircuitBreaker
                  args:
                    name: catalogService
                    fallbackUri: forward:/fallback/catalog

resilience4j:
  circuitbreaker:
    instances:
      catalogService:
        slidingWindowType: COUNT_BASED
        slidingWindowSize: 20
        minimumNumberOfCalls: 10
        failureRateThreshold: 50
        waitDurationInOpenState: 10s
        permittedNumberOfCallsInHalfOpenState: 5

A fallback should be explicit about degraded data. Do not return a normal-looking response that silently omits critical fields.

@RestController
@RequestMapping("/fallback")
public class GatewayFallbackController {

    @GetMapping("/catalog")
    public ResponseEntity<ProblemDetail> catalogFallback() {
        ProblemDetail problem = ProblemDetail.forStatus(
                HttpStatus.SERVICE_UNAVAILABLE
        );

        problem.setTitle("Catalog temporarily unavailable");
        problem.setDetail(
                "The catalog service did not respond within the edge latency budget."
        );
        problem.setProperty("degraded", true);

        return ResponseEntity
                .status(HttpStatus.SERVICE_UNAVAILABLE)
                .body(problem);
    }
}

Fallbacks are suitable for optional content, cached summaries, or explicit service-unavailable responses. They should not fabricate successful financial, inventory, or authorization outcomes.

Authenticate at the Edge, Authorize in Depth

The gateway can validate a JWT before routing a request. Downstream services should still enforce authorization for their own resources and invariants.

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://identity.example.com/realms/platform
@Configuration
@EnableWebFluxSecurity
public class SecurityConfiguration {

    @Bean
    SecurityWebFilterChain gatewaySecurity(ServerHttpSecurity http) {
        return http
                .csrf(ServerHttpSecurity.CsrfSpec::disable)
                .authorizeExchange(exchange -> exchange
                        .pathMatchers(
                                "/actuator/health",
                                "/actuator/info",
                                "/public/**"
                        ).permitAll()
                        .pathMatchers(HttpMethod.GET, "/api/catalog/**")
                        .hasAuthority("SCOPE_catalog.read")
                        .pathMatchers("/api/orders/**")
                        .hasAuthority("SCOPE_orders.write")
                        .anyExchange()
                        .authenticated()
                )
                .oauth2ResourceServer(resourceServer ->
                        resourceServer.jwt(Customizer.withDefaults())
                )
                .build();
    }
}

Do not trust identity headers from clients

A client can send a header such as:

X-Internal-User: admin

unless the edge explicitly removes it.

Safer options include:

  1. forward the validated bearer token and let downstream resource servers validate it;
  2. use token relay when the gateway acts as an OAuth 2.0 client;
  3. strip inbound identity headers and create new signed or network-trusted headers;
  4. protect the gateway-to-service network with mTLS or equivalent workload identity.

If a downstream service trusts a gateway-generated identity header, it must not be reachable through a path where a client can provide that header directly.

Rate Limiting Needs a Stable Key

Rate limiting can protect:

  • login and token endpoints;
  • expensive search routes;
  • partner APIs with contractual quotas;
  • endpoints exposed to automated abuse;
  • dependencies with a known capacity ceiling.

A KeyResolver can derive the key from an authenticated principal:

@Configuration
public class RateLimitConfiguration {

    @Bean
    KeyResolver principalKeyResolver() {
        return exchange -> exchange
                .getPrincipal()
                .map(Principal::getName)
                .switchIfEmpty(
                        Mono.just(
                                exchange.getRequest()
                                        .getRemoteAddress()
                                        .getAddress()
                                        .getHostAddress()
                        )
                );
    }
}

With the Redis rate limiter dependency and Redis connection configured, a route can use:

spring:
  cloud:
    gateway:
      server:
        webflux:
          routes:
            - id: search-service
              uri: lb://search-service
              predicates:
                - Path=/api/search/**
              filters:
                - name: RequestRateLimiter
                  args:
                    key-resolver: "#{@principalKeyResolver}"
                    redis-rate-limiter.replenishRate: 20
                    redis-rate-limiter.burstCapacity: 40
                    redis-rate-limiter.requestedTokens: 1

IP-based limiting alone is weak when many users share a NAT address or attackers rotate source addresses. Prefer an authenticated tenant, account, API key, or client identifier when available.

Keep Body Transformation Small and Typed

String replacement is not safe JSON transformation:

body.replace("}", ", \"processedByGateway\": true}")

It can break nested objects, arrays, whitespace, escaped values, or non-JSON responses.

Use typed deserialization for small transformations:

public record ProductResponse(
        String id,
        String name,
        BigDecimal price
) {}

public record ClientProductResponse(
        String id,
        String displayName,
        BigDecimal price
) {}

For large bodies, streaming responses, file downloads, or high-volume routes, avoid gateway-side body transformation. Buffering a response increases memory use and delays the first byte sent to the client.

When the transformation represents a client-specific contract rather than generic edge policy, a BFF is usually the clearer home.

Implement BFF Composition as an Application Boundary

A dashboard endpoint often needs data from multiple services. Hiding that orchestration inside a custom GatewayFilter with a dummy no://op URI makes error handling and response writing harder than necessary.

For a limited aggregation endpoint in the same deployment, use an ordinary reactive controller and a dedicated service. Do not define a Gateway route for the same path.

public record UserView(
        String id,
        String name,
        String email
) {}

public record OrderView(
        String orderId,
        BigDecimal total,
        Instant createdAt
) {}

public record DashboardResponse(
        UserView user,
        List<OrderView> recentOrders,
        List<String> warnings
) {}
@Configuration
public class DownstreamClientConfiguration {

    @Bean
    WebClient userClient(WebClient.Builder builder) {
        return builder
                .baseUrl("http://user-service:8081")
                .build();
    }

    @Bean
    WebClient orderClient(WebClient.Builder builder) {
        return builder
                .baseUrl("http://order-service:8082")
                .build();
    }
}
@Service
public class DashboardService {

    private final WebClient userClient;
    private final WebClient orderClient;

    public DashboardService(
            @Qualifier("userClient") WebClient userClient,
            @Qualifier("orderClient") WebClient orderClient
    ) {
        this.userClient = userClient;
        this.orderClient = orderClient;
    }

    public Mono<DashboardResponse> load(String userId) {
        Mono<UserView> requiredUser = userClient
                .get()
                .uri("/users/{userId}", userId)
                .retrieve()
                .bodyToMono(UserView.class);

        Mono<OrderResult> optionalOrders = orderClient
                .get()
                .uri(uriBuilder -> uriBuilder
                        .path("/orders")
                        .queryParam("userId", userId)
                        .queryParam("limit", 10)
                        .build()
                )
                .retrieve()
                .bodyToFlux(OrderView.class)
                .collectList()
                .map(orders -> new OrderResult(
                        orders,
                        List.<String>of()
                ))
                .onErrorResume(exception ->
                        Mono.just(
                                new OrderResult(
                                        List.of(),
                                        List.of(
                                                "Recent orders are temporarily unavailable"
                                        )
                                )
                        )
                );

        return Mono.zip(requiredUser, optionalOrders)
                .map(tuple -> new DashboardResponse(
                        tuple.getT1(),
                        tuple.getT2().orders(),
                        tuple.getT2().warnings()
                ));
    }

    private record OrderResult(
            List<OrderView> orders,
            List<String> warnings
    ) {}
}
@RestController
@RequestMapping("/api/dashboard")
public class DashboardController {

    private final DashboardService dashboardService;

    public DashboardController(DashboardService dashboardService) {
        this.dashboardService = dashboardService;
    }

    @GetMapping("/{userId}")
    Mono<DashboardResponse> dashboard(
            @PathVariable String userId
    ) {
        return dashboardService.load(userId);
    }
}

This design makes the failure policy visible:

  • user data is required, so failure fails the request;
  • recent orders are optional, so failure produces an empty list and a warning;
  • Mono.zip starts the independent requests without blocking;
  • response construction remains testable outside the low-level gateway filter chain.

For a larger BFF, move this controller and service to a separate deployment with its own scaling, release cycle, and ownership.

Where Java Virtual Threads Fit

Spring Cloud Gateway Server WebFlux uses Reactor and Netty. Its event-loop model already avoids dedicating a platform thread to every waiting network request.

Virtual threads therefore are not a switch that makes the reactive gateway faster.

They are useful when an application intentionally uses a blocking programming model, such as:

  • a downstream Spring MVC service that calls JDBC;
  • a separate imperative BFF;
  • a compatibility adapter for a blocking SDK;
  • administrative tasks that do not run on the Netty event loop.

Java virtual threads help blocking I/O scale with a thread-per-task style. They do not make CPU-intensive work faster, and they do not make blocking on the Netty event loop safe.

Inside a reactive Gateway filter, this is dangerous:

@Override
public Mono<Void> filter(
        ServerWebExchange exchange,
        GatewayFilterChain chain
) {
    legacyBlockingClient.loadCustomer(); // Blocks the event loop
    return chain.filter(exchange);
}

The preferred options are:

  1. use a non-blocking client;
  2. move the integration behind a downstream adapter service;
  3. isolate unavoidable blocking work on a scheduler designed for blocking tasks;
  4. use a separate imperative BFF built around virtual threads.

Do not maintain two concurrency models in the gateway without a concrete reason and measurements.

Observability at the Edge

A gateway should answer four operational questions:

  1. Which route handled the request?
  2. How long did routing and the downstream call take?
  3. Which dependency or policy caused the failure?
  4. Can the request be traced across services?

Spring Boot Actuator and Micrometer provide the foundation for metrics and tracing.

Useful gateway measurements include:

  • request count by route and status;
  • latency percentiles by route;
  • active requests;
  • connection pool saturation;
  • circuit-breaker state and rejection count;
  • rate-limit rejection count;
  • downstream timeout count;
  • retry count;
  • response size;
  • authentication failure count.

Avoid high-cardinality metric tags such as raw user IDs, full URLs with identifiers, bearer tokens, or arbitrary query strings.

For logs, include a correlation or trace ID and route ID, but do not record authorization headers, session cookies, passwords, or unfiltered request bodies.

Protect Actuator Endpoints

Gateway actuator endpoints can expose route definitions and operational details. Do not publish every endpoint to the public internet.

A conservative configuration is:

management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
  endpoint:
    health:
      probes:
        enabled: true
      show-details: never

Expose administrative Gateway route inspection only on a private management network with authentication.

Deployment and Scaling

Keep the gateway stateless so multiple replicas can run behind a load balancer.

Production considerations include:

  • at least two replicas across failure domains;
  • readiness probes that stop traffic before shutdown;
  • graceful shutdown with a sufficient drain period;
  • bounded connection pools and pending acquisition queues;
  • route configuration versioning;
  • secret management outside source control;
  • separate dashboards for gateway and downstream latency;
  • load tests that include slow and failing dependencies;
  • resource limits that leave headroom for network buffers.

Do not deploy the gateway as a Kubernetes StatefulSet unless it genuinely owns stable identity or storage. A stateless Deployment is the normal choice.

Failure Policy Matrix

Write the edge policy before implementing filters.

FailureTypical gateway responseRetry?Fallback?
Invalid or missing JWT401 UnauthorizedNoNo
Authenticated but forbidden403 ForbiddenNoNo
Rate limit exceeded429 Too Many RequestsClient-controlledNo
Downstream connection failure on GET502 Bad GatewayLimited retryPossibly
Downstream response timeout504 Gateway TimeoutLimited and budgetedPossibly
Invalid client payload400 Bad RequestNoNo
Downstream domain rejectionPreserve suitable 4xxNoNo
Circuit open503 Service UnavailableNo immediate retryExplicit degraded response
Gateway internal error500 Internal Server ErrorNo automatic retryNo

A fallback is a product decision, not just a technical switch. The client contract must explain whether data is stale, partial, or unavailable.

Testing Strategy

Route tests

Verify:

  • the expected route matches;
  • path rewriting is correct;
  • blocked headers are removed;
  • public and protected paths follow security rules;
  • fallback routes return the documented status.

Resilience tests

Simulate:

  • connection refusal;
  • slow response beyond the route timeout;
  • intermittent 502 responses;
  • an open circuit breaker;
  • exhausted rate-limit tokens;
  • a client disconnect during downstream processing.

BFF tests

Verify:

  • independent calls run concurrently;
  • required dependency failure fails the response;
  • optional dependency failure produces the warning contract;
  • empty collections remain valid JSON arrays;
  • cancellation propagates to outstanding reactive calls where possible.

Use WebTestClient for the gateway surface and stub HTTP servers or containers for downstream dependencies. Do not mock every network boundary and assume the route configuration works.

Troubleshooting

A route returns 404

Check:

  • whether the path predicate matches the actual request;
  • whether a controller and Gateway route conflict;
  • whether route configuration uses the current property namespace;
  • whether the configuration profile was loaded;
  • whether the route appears in the private Gateway actuator view.

A route returns 503 with lb://

Check:

  • whether a DiscoveryClient implementation is configured;
  • whether the service ID matches registration;
  • whether healthy instances are available;
  • whether spring-cloud-starter-loadbalancer is present;
  • whether DNS or registry access works from the gateway container.

Throughput collapses under load

Look for:

  • blocking SDK calls on Reactor Netty threads;
  • response body buffering;
  • unbounded logging;
  • connection pool exhaustion;
  • retries multiplying downstream load;
  • CPU-heavy transformations;
  • a slow identity provider or JWK endpoint;
  • rate limiter or Redis latency.

The circuit breaker never opens

Check:

  • whether the CircuitBreaker starter is on the classpath;
  • whether the filter wraps the intended route;
  • whether failures match the configured threshold;
  • whether the minimum number of calls has been reached;
  • whether retries are hiding or multiplying failures.

JWT authentication works at the gateway but fails downstream

Decide which trust model is intended:

  • forward the bearer token and validate it again downstream;
  • relay an OAuth access token;
  • exchange it for a service-specific token;
  • generate trusted identity headers and prevent all bypass paths.

Do not accidentally mix these models.

Conclusion

A production API Gateway should be boring in the best sense: predictable routes, bounded latency, explicit security, controlled degradation, and measurable behavior.

For a Spring Boot 4 and Spring Cloud Gateway design:

  • use a compatible Spring Cloud release train;
  • use the Gateway Server WebFlux starter and current route namespace;
  • keep blocking work off the Netty event loop;
  • configure timeouts before retries;
  • retry only safe or explicitly idempotent operations;
  • use the CircuitBreaker filter rather than retired Hystrix examples;
  • validate JWTs at the edge while preserving downstream authorization;
  • move complex client composition into a visible BFF boundary;
  • treat virtual threads as a tool for blocking applications, not a reactive gateway accelerator;
  • test failure windows, not only successful routing.

The gateway should reduce complexity for clients without concentrating every form of complexity inside itself.

Official References