title: Why is the handler for a REST endpoint being accessed twice, when accessed from a WebClient?
body: <p>This is a second attempt, with revised demo code that, hopefully, better illustrates the issue. The code has been stripped down to remove all elements except those demonstrating the issue being encountered.</p>
  
  <p><strong>Add Note: <em>Some additional testing was done, and the results posted as an answer (vice extending this post). It may be that this is "expected behavior", but I'm still trying to understand "the why".</em></strong></p>
  
  <p>The code "works", in that it returns the expected information (either a String, or a list of Strings). However, when a WebClient is used to access a REST endpoint (<em>localhost:8080/test/DemoClient</em>) that returns a Flux, two calls are being made into the associated handler (<em>DemoMainHandler.getAll()</em>). I do not see where the second call is being made on <em>DemoMainHandler.getAll()</em>, but am concerned about potential performance issues if this occurs in a production environment.</p>
  
  <p>In the code provided, everything is running under a single Spring Webflux application, so there is not a separate process for the <em>DemoClient</em> code.</p>
  
  <p>Accessing the REST endpoint at <em>localhost:8080/test/DemoClient/2</em> appears to work correctly, returning a Mono to Postman with the value "Only One". More importantly, <em>DemoMainHandler.getById()</em> is only invoked once.</p>
  
  <p>However, accessing the REST endpoint at <em>localhost:8080/test/DemoClient</em> produces results that are a bit concerning. The String values returned to Postman via a Flux appear okay, but</p>
  
  <ol>
  <li><em>DemoClientHandler.getAll()</em> invoked when REST endpoint is accessed</li>
  <li>*DemoClientHandler.getAll() invokes <em>DemoClient.getAll()</em></li>
  <li><em>DemoClient.getAll()</em> uses a <em>WebClient</em> to access the REST endpoint at <em>localhost:8080/test/DemoMain</em></li>
  <li><em>DemoClient.getAll()</em> uses <em>flatMapMany</em> to iterate over the returned <em>ClientResponse</em> and extract the Flux from the response body</li>
  <li>The Flux generated by <em>DemoCLient.getAll()</em> is returned to <em>DemoClientHandler.getAll()</em></li>
  <li><em>DemoClientHandler.getAll()</em> inspects the Flux, determines it has one-or-more elements, and returns the Flux in a <em>ServerResponse</em> to the initial client (in this case, Postman)</li>
  <li>Postman then unpacks the Flux (map? flatMap?) and displays the returned Strings ("CallMeOnce")</li>
  </ol>
  
  <p>What I do not understand is why <em>DemoClientHandler.getAll()</em> is being invoked a second time, as indicated by the second <em>System.out.println()</em> output to the console. It appears to be associated with the use of Flux as a return type?</p>
  
  <p>Add note: On the chance that the issue was somehow driven by the <em>.exchange().flatMapMany()</em> construct, I tried using <em>.retrieve().bodyToFlux()</em> construct (see commented code in <em>DemoClient</em>). Same result (i.e., <em>DemoMainHandler.getAll()</em> appears to be invoked twice).</p>
  
  <p>Console Output</p>
  
  <pre><code>2019-10-07 08:16:18.953  INFO 9384 --- [           main] c.example.testdupe.TestDupeApplication   : Starting TestDupeApplication on M7730-LFR with PID 9384 (D:\sandbox\TestDupe\build\classes\java\main started by LesR in D:\sandbox\TestDupe)
  2019-10-07 08:16:18.953  INFO 9384 --- [           main] c.example.testdupe.TestDupeApplication   : No active profile set, falling back to default profiles: default
  2019-10-07 08:16:20.062  INFO 9384 --- [           main] o.s.b.web.embedded.netty.NettyWebServer  : Netty started on port(s): 8080
  2019-10-07 08:16:20.062  INFO 9384 --- [           main] c.example.testdupe.TestDupeApplication   : Started TestDupeApplication in 1.324 seconds (JVM running for 1.871)
  
  ***** Invoke localhost:8080/test/DemoClient/{id}
  DemoClientHandler.getById( ServerRequest )
  DemoClient.getById( 2 )
  DemoMainHandler.getById( ServerRequest )
  
  ***** Invoke localhost:8080/test/DemoClient
  DemoClientHandler.getAll( ServerRequest )
  DemoClientHandler.getAll() &gt;&gt; BEFORE invoking demoClient.getAll()
  DemoClient.getAll()
  DemoClient.getAll() &gt;&gt; RETURN fluxString
  DemoClientHandler.getAll() &gt;&gt;  AFTER invoking demoClient.getAll()
  DemoMainHandler.getAll( ServerRequest )
  DemoMainHandler.getAll( ServerRequest )
  </code></pre>
  
  <p>Example Code</p>
  
  <pre><code>@SpringBootApplication
  public class TestDupeApplication {
  
      public static void main(String[] args) {
          SpringApplication.run(TestDupeApplication.class, args);
      }
  }
  
  
  @Configuration
  public class DemoClientRouter {
  
      @Bean
      public RouterFunction&lt;ServerResponse&gt; clientRoutes(DemoClientHandler requestHandler) {
          return nest(path("/test"),
                  nest(accept(APPLICATION_JSON),
                          RouterFunctions.route(RequestPredicates.GET("/DemoClient"), requestHandler::getAll)
                                      .andRoute(RequestPredicates.GET("/DemoClient/{id}"), requestHandler::getById)));
      }
  }
  
  
  @Component
  public class DemoClientHandler {
  
      @Autowired
      DemoClient demoClient;
  
      public Mono&lt;ServerResponse&gt; getAll(ServerRequest request) {
          System.out.println("DemoClientHandler.getAll( ServerRequest )");
          System.out.println("DemoClientHandler.getAll() &gt;&gt; BEFORE invoking demoClient.getAll()");
          Flux&lt;String&gt; fluxString = demoClient.getAll();
          System.out.println("DemoClientHandler.getAll() &gt;&gt;  AFTER invoking demoClient.getAll()");
  
          return fluxString.hasElements().flatMap(hasElement -&gt; {
              return hasElement ? ServerResponse.ok()
                                                .contentType(MediaType.APPLICATION_JSON)
                                                .body(fluxString, String.class)
                                : ServerResponse.noContent().build();
          });
      }
  
      public Mono&lt;ServerResponse&gt; getById(ServerRequest request) {
          System.out.println("DemoClientHandler.getById( ServerRequest )");
          Mono&lt;String&gt; monoString;
  
          return demoClient.getById( 2 ).flatMap(stringVal -&gt; ServerResponse.ok()
                                                                            .contentType(MediaType.APPLICATION_JSON)
                                                                            .body(Mono.just(stringVal), String.class))
                                        .switchIfEmpty(ServerResponse.notFound().build());
      }
  }
  
  
  @Component
  public class DemoClient {
  
      private final WebClient client;
  
      public DemoClient() {
          client = WebClient.create();
      }
  
      public Flux&lt;String&gt; getAll() {
          System.out.println("DemoClient.getAll()");
          Flux&lt;String&gt; fluxString;
  
          fluxString = client.get().uri("http://localhost:8080/test/DemoMain")
                                   .accept(MediaType.APPLICATION_JSON)
                                   .exchange()
                                   .flatMapMany(response -&gt; response.bodyToFlux(String.class));
  
          // fluxString = client.get().uri("http://localhost:8080/test/DemoMain")
          //                          .accept(MediaType.APPLICATION_JSON)
          //                          .retrieve()
          //                          .bodyToFlux(String.class);
  
          System.out.println("DemoClient.getAll() &gt;&gt; RETURN fluxString");
          return fluxString;
      }
  
      public Mono&lt;String&gt; getById(int id) {
          System.out.printf("DemoClient.getById( %d )%n", id);
          return client.get().uri("http://localhost:8080/test/DemoMain/" + id)
                             .accept(MediaType.APPLICATION_JSON)
                             .exchange()
                             .flatMap(response -&gt; response.bodyToMono(String.class));
      }
  }
  
  
  @Configuration
  public class DemoMainRouter {
      @Bean
      public RouterFunction&lt;ServerResponse&gt; demoPOJORoute(DemoMainHandler requestHandler) {
          return nest(path("/test"),
                  nest(accept(APPLICATION_JSON),
                          RouterFunctions.route(RequestPredicates.GET("/DemoMain"), requestHandler::getAll)
                                      .andRoute(RequestPredicates.GET("/DemoMain/{id}"), requestHandler::getById)));
      }
  }
  
  
  @Component
  public class DemoMainHandler {
  
      public Mono&lt;ServerResponse&gt; getAll(ServerRequest request) {
          System.out.println("DemoMainHandler.getAll( ServerRequest )");
  
          return ServerResponse.ok()
                               .contentType(MediaType.APPLICATION_JSON)
                               .body(Flux.just("Call", "Me", "Once"), String.class);
      }
  
      public Mono&lt;ServerResponse&gt; getById(ServerRequest request) {
          System.out.println("DemoMainHandler.getById( ServerRequest )");
  
          return ServerResponse.ok()
                               .contentType(MediaType.APPLICATION_JSON)
                               .body(Mono.just("Only One"), String.class);
      }
  }
  </code></pre>
  
  <p>This code added to support follow-on discussion...</p>
  
  <pre><code>@Component
  public class DemoClient {
  
      private final WebClient client;
  
      public DemoClient() {
          client = WebClient.create();
      }
  
      public Flux&lt;String&gt; getAll() {
          Flux&lt;String&gt; fluxString;
  
          Mono&lt;ClientResponse&gt; monoCR = client.get().uri("http://localhost:8080/test/DemoMain")
                                                    .accept(MediaType.APPLICATION_JSON)
                                                    .exchange();
  
          fluxString = monoCR.flatMapMany(clientResponse -&gt; clientResponse.bodyToFlux(String.class));
  
  //        fluxString.subscribe();
  //        return fluxString;
          return Flux.just("Foo", "Bar");
      }
  </code></pre>