
One of the most common challenges developers face with Spring Cloud Gateway is handling the request body inside filters.
If youβve ever tried to read the body in a custom filter, you might have noticed something strange:
π After reading the body, downstream filters or services receive an empty body.
This happens because reactive streams in Spring WebFlux can only be consumed once.
Hereβs a typical scenario:
@Component@Order(1)public class RequestValidationFilter implements GlobalFilter {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {ServerHttpRequest request = exchange.getRequest();// β PROBLEM: This consumes the request body streamreturn request.getBody().collectList().flatMap(dataBuffers -> {String requestBody = dataBuffers.stream().map(dataBuffer -> {byte[] bytes = new byte[dataBuffer.readableByteCount()];dataBuffer.read(bytes);DataBufferUtils.release(dataBuffer);return new String(bytes, StandardCharsets.UTF_8);}).collect(Collectors.joining());if (requestBody.contains("forbidden")) {exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);return exchange.getResponse().setComplete();}// β Downstream services now receive an empty bodyreturn chain.filter(exchange);});}}
Problem: once the body is consumed here, it canβt be read again by downstream services.
The fix involves a two-phase approach:
We first create a filter that runs before everything else (@Order(-1)) and caches the request body.
@Component@Order(-1) // Run firstpublic class CacheRequestBodyFilter implements GlobalFilter {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {return DataBufferUtils.join(exchange.getRequest().getBody()).flatMap(dataBuffer -> {byte[] bytes = new byte[dataBuffer.readableByteCount()];dataBuffer.read(bytes);DataBufferUtils.release(dataBuffer);// Cache body in exchange attributesexchange.getAttributes().put("CACHED_REQUEST_BODY", new String(bytes, StandardCharsets.UTF_8));// Create reusable request decoratorServerHttpRequest decoratedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {@Overridepublic Flux<DataBuffer> getBody() {return Flux.just(exchange.getResponse().bufferFactory().wrap(bytes));}};return chain.filter(exchange.mutate().request(decoratedRequest).build());});}}
Later filters can simply read from cache instead of consuming the stream.
@Component@Order(1) // Run after cachingpublic class RequestValidationFilter implements GlobalFilter {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {String cachedBody = exchange.getAttribute("CACHED_REQUEST_BODY");if (cachedBody != null && cachedBody.contains("forbidden")) {exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);return exchange.getResponse().setComplete();}return chain.filter(exchange);}}
Working with reactive request bodies in Spring Cloud Gateway can be tricky. By implementing a cache + decorator pattern, you can safely read the body multiple times without breaking downstream processing. This approach is ideal for:
Thank you for reading our comprehensive guide on "Spring Cloud Gateway: How to Read Request Body Without Breaking Downstream Processing" We hope you found it insightful and valuable. If you have any questions, need further assistance, or are looking for expert support in developing and managing your projects. our team is here to help!
Reach out to us for Your Project Needs:
π Website: https://www.prometheanz.com
π§ Email: [email protected]
Copyright Β© 2025 PrometheanTech. All Rights Reserved.