Techniques to Handle Redirect URLs with Trailing Slashes in Spring Cloud Gateway — Explained with Real Java Example
Imagine this: you deploy a Spring Cloud Gateway in front of multiple microservices. Everything runs smoothly — until one day, users start reporting that /login redirects to /login/ endlessly, or worse, returns a 404. What happened?The problem isn...

I am Tuanh.net. As of 2024, I have accumulated 8 years of experience in backend programming. I am delighted to connect and share my knowledge with everyone.
1. The Subtle Problem Behind Trailing Slashes
/login≠/login//api/v1/users≠/api/v1/users/
RouteLocator in Spring Cloud Gateway evaluates the path predicates sequentially.Each Predicate (like Path=/login/**) matches exactly the given structure — meaning /login may match but /login/ may not.
SetStatusGatewayFilterFactory or RedirectToGatewayFilterFactory), the resulting Location header might contain or lack the trailing /, depending on how you’ve defined it.This inconsistency triggers client-side confusion and sometimes double redirects.
2. The Correct Way to Handle Redirect URLs with Trailing Slashes
/.
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class TrailingSlashNormalizerFilter implements GatewayFilter, Ordered {
@Override
public Mono<void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String rawPath = exchange.getRequest().getURI().getPath();
if (rawPath.length() > 1 && rawPath.endsWith("/")) {
String newPath = rawPath.substring(0, rawPath.length() - 1);
exchange = exchange.mutate()
.request(exchange.getRequest().mutate()
.path(newPath)
.build())
.build();
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return -100; // Run early before route matching
}
}
</void>
- The filter checks if the request path ends with
/. - It trims the trailing slash but skips the root
/. - The
exchange.mutate()call rebuilds the request before routing continues. - Setting order to
-100ensures it runs before route predicates.
/login and /login/ resolve to the same target route without needing messy rewrite rules.
RewritePath filter in application.yml:
spring:
cloud:
gateway:
routes:
- id: login-service
uri: http://localhost:8081
predicates:
- Path=/login/**, /login
filters:
- RewritePath=/login(?<segment>/?)$, /login
</segment>
- The regular expression
(? /?)captures the optional trailing slash. - The replacement
/loginensures a consistent form before redirecting to the target service.
/login and /login/ behave identically from the client’s perspective.
filters:
- name: RedirectTo
args:
status: 302
url: https://example.com/login/
3. Broader Considerations
/docs and /docs/ are seen as two distinct URLs.If you’re using Spring Cloud Gateway in front of a static docs site (like Swagger UI or a React SPA), inconsistent slashes can lead to duplicate content penalties.Normalize them early at the gateway level to ensure consistent canonical URLs.
4. Key Takeaways
/, apply that rule globally. Don’t mix conventions across microservices.
curl -I http://localhost:8080/login
curl -I http://localhost:8080/login/
5. Conclusion
RewritePath, and being consistent with redirect targets, you ensure smoother routing, cleaner SEO, and fewer headaches at 2 a.m.
Read more at : Techniques to Handle Redirect URLs with Trailing Slashes in Spring Cloud Gateway — Explained with Real Java Example





