title: How to do sequence of operations and ensure one operation is complete before next one in Spring Reactor web app?
body: <p>I have Spring Boot 2 web app in which I need to identify site visitor by cookie and gather page view stats. So I need to intercept every web request.  The code I had to write is more complex than call back hell (the very problem Spring reactor was supposed to solve).  </p>
  
  <p>Here is the code:</p>
  
  <pre><code>package mypack.conf;
  
  import java.time.LocalDateTime;
  import java.util.ArrayList;
  import java.util.List;
  import java.util.Map;
  import java.util.UUID;
  
  import org.springframework.beans.factory.annotation.Autowired;
  import org.springframework.beans.factory.annotation.Value;
  import org.springframework.context.annotation.Bean;
  import org.springframework.context.annotation.ComponentScan;
  import org.springframework.context.annotation.Configuration;
  import org.springframework.context.annotation.Lazy;
  import org.springframework.data.mongodb.repository.config.EnableReactiveMongoRepositories;
  import org.springframework.http.HttpCookie;
  import org.springframework.http.ResponseCookie;
  import org.springframework.web.reactive.config.ResourceHandlerRegistry;
  import org.springframework.web.reactive.config.WebFluxConfigurer;
  import org.springframework.web.server.ServerWebExchange;
  import org.springframework.web.server.WebFilter;
  import org.springframework.web.server.WebFilterChain;
  
  import mypack.dao.PageViewRepository;
  import mypack.dao.UserRepository;
  import mypack.domain.PageView;
  import mypack.domain.User;
  import mypack.security.JwtProvider;
  
  import reactor.core.publisher.Mono;
  @Configuration
  
  
  @ComponentScan(basePackages = "mypack")
  @EnableReactiveMongoRepositories(basePackages = "mypack")
  public class WebConfig implements WebFluxConfigurer {
  
      @Autowired
      @Lazy
      private UserRepository userRepository;
  
      @Autowired
      @Lazy
      private PageViewRepository pageViewRepository;
  
  
      @Autowired
      @Lazy
      JwtProvider jwtProvider;
  
  
      @Bean
      public WebFilter sampleWebFilter()  {
          return new WebFilter() {
  
              @Override
              public Mono&lt;Void&gt; filter(ServerWebExchange exchange, WebFilterChain chain) {
  
                  String uri = exchange.getRequest().getURI().toString();
                  String path = exchange.getRequest().getPath().pathWithinApplication().value();
  
  
                  HttpCookie  cookie = null;
                  String token = "";
                  Map&lt;String, List&lt;HttpCookie&gt;&gt; cookies = exchange.getRequest().getCookies();
  
  
                  try {
                      if((exchange.getRequest().getCookies().containsKey("_token") )
                              &amp;&amp;  (exchange.getRequest().getCookies().getFirst("_token"))!=null  ) {
  
                          cookie = exchange.getRequest().getCookies().getFirst("_token");
                          token = cookie.getValue();
  
  
                          return userRepository.findByToken(token).map(user -&gt; {
  
                                  exchange.getAttributes().put("_token", user.getToken());
  
  
                                  PageView pg = PageView.builder().createdDate(LocalDateTime.now()).URL(uri).build();
                                  pageViewRepository.save(pg).subscribe(pg1 -&gt; {user.getPageviews().add(pg1); });
  
                                  userRepository.save(user).subscribe();
                                      return user;
                              })
  
  
                              .flatMap(user-&gt; chain.filter(exchange)); // ultimately this step executes regardless user exist or not
  
                      // handle case when brand new user first time visits website    
                      } else {
                          token = jwtProvider.genToken("guest", UUID.randomUUID().toString());
                          User user = User.builder().createdDate(LocalDateTime.now()).token(token).emailId("guest").build();
                          userRepository.save(user).subscribe();
                          exchange.getResponse().getCookies().remove("_token");
  
                          ResponseCookie rcookie  = ResponseCookie.from("_token", token).httpOnly(true).build();
                          exchange.getResponse().addCookie(rcookie);
                          exchange.getAttributes().put("_token", token);
  
                      }
  
                  } catch (Exception e) {
  
                      e.printStackTrace();
                  }
  
  
  
                  return chain.filter(exchange);
              } // end of  Mono&lt;Void&gt; filter method
          }; // end of New WebFilter (anonymous class)
      }
  
  }
  </code></pre>
  
  <p>Other relevant classes:</p>
  
  <pre><code>@Repository
  public interface PageViewRepository extends   ReactiveMongoRepository&lt;PageView, String&gt;{
  
      Mono&lt;PageView&gt; findById(String id);
  
  }
  
  
  @Repository
  public interface UserRepository extends   ReactiveMongoRepository&lt;User, String&gt;{
  
      Mono&lt;User&gt; findByToken(String token);
  
  }
  
  
  
  
  
  @Data
  @AllArgsConstructor
  @Builder
  @NoArgsConstructor
  public class User {
  
      @Id
      private String id;
      private String token;
  
  
      @Default
      private LocalDateTime createdDate = LocalDateTime.now();
  
      @DBRef
      private List&lt;PageView&gt; pageviews;
  
  }
  
  
  
  Data
  @Document
  @Builder
  public class PageView {
      @Id
      private String id;
  
      private String URL;
  
      @Default
      private LocalDateTime createdDate = LocalDateTime.now();
  }
  </code></pre>
  
  <p>Relevant part of gradle file:</p>
  
  <pre><code>buildscript {
      ext {
  
          springBootVersion = '2.0.1.RELEASE'
      }
      repositories {
          mavenCentral()
  
      }
      dependencies {
          classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
      }
  }
  
  dependencies {
  
      compile('org.springframework.boot:spring-boot-starter-data-mongodb-reactive')
      compile('org.springframework.boot:spring-boot-starter-security')
      compile('org.springframework.boot:spring-boot-starter-thymeleaf')
  
      compile('org.springframework.boot:spring-boot-starter-webflux')
  
      compile('org.springframework.security:spring-security-oauth2-client')
      compile('org.springframework.security.oauth:spring-security-oauth2:2.3.4.RELEASE')
      runtime('org.springframework.boot:spring-boot-devtools')
      compileOnly('org.projectlombok:lombok')
      compile "org.springframework.security:spring-security-jwt:1.0.9.RELEASE"
      compile "io.jsonwebtoken:jjwt:0.9.0"
  
      testCompile('org.springframework.boot:spring-boot-starter-test')
  
      testCompile('io.projectreactor:reactor-test')
  
      compile('com.fasterxml.jackson.core:jackson-databind')
  }
  </code></pre>
  
  <p>The problem is in these lines:</p>
  
  <blockquote>
    <p>PageView pg =
    PageView.builder().createdDate(LocalDateTime.now()).URL(uri).build();
                                  pageViewRepository.save(pg).subscribe(pg1 -> {user.getPageviews().add(pg1); });</p>
  </blockquote>
  
  <p>which hangs the browser (keeps waiting for response).</p>
  
  <p>Basically what I want is this:
  Must not use block() which does not even work in webfilter code as block also
  hangs the browser.
  Save pageview in mongo db. After it is saved, pageview has valid mongodb id which is needed to be stored as reference in pageviews List of user entity. Therefore only after it is saved in db, the next step is update user's pageviews List.
  Next step is save the updated user without effecting downstream controller methods which may also update user and may need to save the user too.
  All this should work in the given WebFilter context.</p>
  
  <p>How to solve this problem?</p>
  
  <p>The solution provided must make sure that user is saved in webfilter before passing on to controller actions some of which also saves user with different values from query string params.</p>