Backend Development 10 min read

Resolving Duplicate Access‑Control‑Allow‑Origin Header Issues in Spring Cloud Gateway

This article explains why Spring Cloud Gateway can return duplicate Access‑Control‑Allow‑Origin and Vary headers during CORS handling, analyzes the underlying processing flow, and provides two practical solutions—using DedupeResponseHeader configuration or implementing a custom GlobalFilter—to ensure a single, correct header is sent to the client.

Top Architect
Top Architect
Top Architect
Resolving Duplicate Access‑Control‑Allow‑Origin Header Issues in Spring Cloud Gateway

When developing a Spring Cloud Gateway‑based micro‑service architecture, developers often encounter CORS errors such as "multiple Access‑Control‑Allow‑Origin headers" because both the backend service and the gateway add the same header.

The article first shows a typical global CORS bean configuration for a Spring Boot service and the equivalent YAML configuration for the gateway, then reproduces the error using a PostMan request that sets Origin: * and receives two Access‑Control‑Allow‑Origin values.

Through a step‑by‑step analysis of the Spring Cloud Gateway request flow, it reveals that the DispatcherHandler delegates to RoutePredicateHandlerMapping , which eventually invokes DefaultCorsProcessor . This processor adds the CORS headers based on the configured CorsConfiguration . Later, the NettyRoutingFilter merges the response headers from the downstream service without deduplication, causing duplicate headers.

Two remediation approaches are presented:

Use DedupeResponseHeader : add a default-filters entry in application.yml such as - DedupeResponseHeader=Vary Access-Control-Allow-Origin Access-Control-Allow-Credentials, RETAIN_FIRST . The DedupeResponseHeaderGatewayFilterFactory removes duplicate values according to the chosen strategy (RETAIN_FIRST, RETAIN_LAST, RETAIN_UNIQUE).

Write a custom GlobalFilter : implement CorsResponseHeaderFilter that runs after NettyWriteResponseFilter (order = NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER + 1 ) and manually deduplicates or selects the desired header values, e.g., keep the first value or replace * when present.

The article also warns about filter ordering: a higher order value means the filter executes later, so to modify the response before it is sent, the custom filter must have an order greater than NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER . It advises using Mono.fromRunnable instead of Mono.defer to avoid re‑executing downstream filters.

Finally, the author provides a concise summary of the two solutions, recommends the RETAIN_FIRST strategy for most cases, and includes several code snippets illustrating the configurations and filter implementations.

backendJavaCORSSpring CloudSpring Cloud Gatewayspring-bootduplicate-header
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.