title: Spring Cloud Gateway - modify response body in global Post filter
body: <p>I have spent the last 2 days trying every possible way of modifying the response body of a request before it hits the client, and nothing seems to work for me. So far I have tried the implementations mentioned <a href="https://gist.github.com/dalegaspi/03550807b1c84100dd028fc8e07950ff" rel="nofollow noreferrer">here</a>, <a href="https://stackoverflow.com/questions/55574382/spring-cloud-gateway-for-composite-api-calls/71888244#71888244">here</a>, <a href="https://stackoverflow.com/questions/50101750/how-to-modify-the-response-body-in-spring-cloud-gateway-just-before-the-commit">here</a>, <a href="https://stackoverflow.com/questions/71465870/how-to-get-original-response-body-in-spring-cloud-gateway-webflux-post-filter">here</a>, <a href="https://gist.github.com/WeirdBob/b25569d461f0f54444d2c0eab51f3c48" rel="nofollow noreferrer">here</a> and a few others that I can't find right now, but nothing has worked. It doesn't matter if I define the filter as pre, post, global, gateway or route-specific - the actual response modification doesn't seem to work for me.</p>
  <p>My situation is the following:
  I have a YAML-configured API gateway running and have configured one of its routes to lead to an ADF service in the background. The issue I have with this ADF application is that the response it returns to the client is in the form of an HTML template that is automatically generated by its backend. In this template, some of the URLs are hardcoded and point to the address of the application itself. To justify the use of an API Gateway in this case, I want to replace those ADF URLs with those of the API Gateway.</p>
  <p>For simplicity's sake, let's say the IP address of my ADF service is <code>1.2.3.4:1234</code>, and the IP address of my API Gateway is <code>localhost:8080</code>. When I hit the ADF route in my gateway, the response contains some auto-generated javascript inserts, such as this one:</p>
  <pre class="lang-js prettyprint-override"><code>AdfPage.PAGE.__initializeSessionTimeoutTimer(1800000, 120000, &quot;http://1.2.3.4:1234/entry/dynamic/index.jspx&quot;);
  </code></pre>
  <p>As you can see, it contains a hardcoded URL. I want to access the response body and find all those hardcoded URLs and replace them with the gateway URL, so the above example becomes:</p>
  <pre class="lang-js prettyprint-override"><code>AdfPage.PAGE.__initializeSessionTimeoutTimer(1800000, 120000, &quot;http://localhost:8080/entry/dynamic/index.jspx&quot;);
  </code></pre>
  <p>To do this, it seems sensible to me to have a global POST filter that kicks in only when the request matches the route for my ADF application, so that's what I've settled on doing.</p>
  <p>Here is my post filter so far:</p>
  <pre class="lang-java prettyprint-override"><code>        @Bean
      public GlobalFilter globalADFUrlReplacementFilter() {
          return (exchange, chain) -&gt; chain.filter(exchange).then(Mono.just(exchange)).map(serverWebExchange -&gt; {
                      ServerHttpRequest request = exchange.getRequest();
                      ServerHttpResponse response = exchange.getResponse();
  
                      if (requestIsTowardsADF(request)) {
                          logger.info(&quot;EXECUTING GLOBAL POST FILTER FOR ADF TEMPLATE URL REPLACEMENT&quot;);
                          ServerHttpResponseDecorator responseDecorator = new ServerHttpResponseDecorator(response) {
  
                              @Override
                              @SuppressWarnings(&quot;unchecked&quot;)
                              public Mono&lt;Void&gt; writeWith(Publisher&lt;? extends DataBuffer&gt; body) {
                                  logger.info(&quot;OVERRIDING writeWith METHOD TO MODIFY THE BODY&quot;);
                                  Flux&lt;? extends DataBuffer&gt; flux = (Flux&lt;? extends DataBuffer&gt;) body;
                                  return super.writeWith(flux.buffer().map(buffer -&gt; {
  
                                      DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
                                      DataBuffer join = dataBufferFactory.join(buffer);
                                      byte[] content = new byte[join.readableByteCount()];
                                      join.read(content);
                                      DataBufferUtils.release(join);
  
                                      String bodyStr = new String(content, StandardCharsets.UTF_8);
                                      bodyStr = bodyStr.replace(ADF_URL, API_GATEWAY_URL);
  
                                      getDelegate().getHeaders().setContentLength(bodyStr.getBytes().length);
                                      return bufferFactory().wrap(bodyStr.getBytes());
                                  }));
                              }
                          };
                          logger.info(&quot;ADF URL REPLACEMENT FILTER DONE&quot;);
                          return chain.filter(serverWebExchange.mutate().request(request).response(responseDecorator).build());
                      }
                      return serverWebExchange;
                  })
                  .then();
      }
  </code></pre>
  <p>And the config:</p>
  <pre class="lang-yml prettyprint-override"><code>spring:
    cloud:
      gateway:
        routes:
          - id: adf-test-2
            uri: http://1.2.3.4:1234
            predicates:
              - Path=/entry/**
  </code></pre>
  <p>You can see that I'm using a <code>org.slf4j.Logger</code> object to log messages in the console. When I run my API Gateway and hit the ADF route, I can see the following:</p>
  <pre class="lang-json prettyprint-override"><code>EXECUTING GLOBAL POST FILTER FOR ADF TEMPLATE URL REPLACEMENT
  ADF URL REPLACEMENT FILTER DONE
  </code></pre>
  <p>And when I check the response I got back from the API Gateway, I can see that the response body is still identical and the ADF URLs have not been replaced at all. I tried debugging the application and as soon as it reaches <code>ServerHttpResponseDecorator responseDecorator = new ServerHttpResponseDecorator(response) {</code> it skips over the entire anonymous class implementation within those curly braces. A testament to that is the absence of the <code>OVERRIDING writeWith METHOD TO MODIFY THE BODY</code> log in the console - it never got executed!</p>
  <p>It seems that for some reason the actual body modification doesn't get executed and I can't figure out why. I tried several different implementations of this filter, as mentioned in the above links, and neither of them worked.</p>
  <p>Can someone please share with me a working POST filter that modifies the response body, or point out the flaw in my solution?</p>
  <p>Thanks a bunch in advance!</p>