title: Spring MVC model keeps attribute from previous request for same mapping
body: <p>When I was testing a controller, I found out that following code works without problems.
  But I do not understand why model contains &quot;passProperty&quot; attribute after returning in post mapping.
  PassProperty class is only annotated with validation constraints.
  In controller it is annotated only with @Valid annotation in a method as you can see in code below.</p>
  <p>I have following mappings in a controller:</p>
  <p>Get:</p>
  <pre><code>@GetMapping(&quot;/accounts/change-password&quot;)
  public ModelAndView changePass(Principal principal) {
      ModelAndView mv = new ModelAndView(&quot;change-password&quot;);
      mv.addObject(&quot;passProperties&quot;, new PassProperties());
  
      return mv;
  }
  </code></pre>
  <p>Post:</p>
  <pre><code>@PostMapping(&quot;/accounts/change-password&quot;)
  public ModelAndView changePass(@Valid PassProperties passProperties, BindingResult bindingResult, Principal principal) {
      ModelAndView mv = new ModelAndView(&quot;change-password&quot;);
  
      if (bindingResult.hasErrors() == false) {
          User user = userService.findUser(principal.getName());
          String oldPass = passProperties.getOldPass();
  
          if (encoder.matches(oldPass, user.getPassword())) {
              user.setPassword(encoder.encode(passProperties.getNewPass1()));
              userService.saveUser(user);
              mv.addObject(&quot;msg&quot;, &quot;Hasło zostało zmienione&quot;);
          } else {
              mv.addObject(&quot;msg&quot;, &quot;Stare hasło nie pasuje.&quot;);
          }
  
      }
  
      return mv;
  }
  </code></pre>
  <h1>Edit 1</h1>
  <p>I am adding following test and output.
  As you can see returned ModelAndView contains passProperties that were not added in the @PostMapping method. The passProperties object is only annotated with @Valid and in PassProperties class fields are annotated with constraints @NotNull, @Length and @NotBlank. It is not annotated anywhere with anything like @ModelAttribute.
  Test:</p>
  <pre><code>@Test
  void changePassPost_whenPassPropertiesHasNoErrors_returnsModelWithPassProperties_And_ChangesPassword() throws Exception {
      User principal = new User (&quot;user&quot;, &quot;user&quot;, true, &quot;LVL99&quot;);
  
      PassProperties passProperties = new PassProperties();
      passProperties.setOldPass(&quot;user&quot;);
      passProperties.setNewPass1(&quot;zaq1&quot;);
      passProperties.setNewPass2(&quot;zaq1&quot;);
  
      when(userService.findUser(&quot;user&quot;)).thenReturn(principal);
      when(encoder.encode(passProperties.getNewPass1())).thenReturn(passProperties.getNewPass1());
      when(encoder.matches(anyString(),anyString())).thenReturn(true);
  
      ModelAndView mv = mockMvc.perform(MockMvcRequestBuilders.post(&quot;/accounts/change-password&quot;)
                  .with(SecurityMockMvcRequestPostProcessors.csrf())
                  .with(SecurityMockMvcRequestPostProcessors.user(new UserDetailsImpl(principal)))
                  .param(&quot;oldPass&quot;, passProperties.getOldPass())
                  .param(&quot;newPass1&quot;, passProperties.getNewPass1())
                  .param(&quot;newPass2&quot;, passProperties.getNewPass2())
              )
              .andDo(MockMvcResultHandlers.print())
              .andExpect(MockMvcResultMatchers.status().isOk())
              .andExpect(MockMvcResultMatchers.model().attributeExists(&quot;passProperties&quot;))
              .andExpect(MockMvcResultMatchers.model().hasNoErrors())
              .andExpect(MockMvcResultMatchers.view().name(&quot;change-password&quot;))
              .andReturn().getModelAndView();
  
      System.out.println(&quot;-------------------------------&quot;);
      System.out.println(mv.getModel().get(&quot;passProperties&quot;));
      System.out.println(mv.getModel().get(&quot;msg&quot;));
      System.out.println(&quot;-------------------------------&quot;);
  
      ArgumentCaptor&lt;User&gt; userCaptor = ArgumentCaptor.forClass(User.class);
      verify(encoder, times(1)).encode(passProperties.getNewPass1());
      verify(userService, times(1)).saveUser(userCaptor.capture());
      assertEquals(passProperties.getNewPass1(), userCaptor.getValue().getPassword());
      assertEquals(passProperties.getNewPass1(), principal.getPassword());
  
  }
  </code></pre>
  <p>Output:</p>
  <pre><code>MockHttpServletRequest:
        HTTP Method = POST
        Request URI = /accounts/change-password
         Parameters = {oldPass=[user], newPass1=[zaq1], newPass2=[zaq1], _csrf=[8fc305ee-f0eb-42a6-8588-cce3ffdba469]}
            Headers = []
               Body = null
      Session Attrs = {SPRING_SECURITY_CONTEXT=org.springframework.security.core.context.SecurityContextImpl@a37a47df: Authentication: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@a37a47df: Principal: com.example.JabaVeans.service.UserDetailsImpl@58e46572; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: LVL99}
  
  Handler:
               Type = com.example.JabaVeans.controller.AccountsController
             Method = com.example.JabaVeans.controller.AccountsController#changePass(PassProperties, BindingResult, Principal)
  
  Async:
      Async started = false
       Async result = null
  
  Resolved Exception:
               Type = null
  
  ModelAndView:
          View name = change-password
               View = null
          Attribute = passProperties
              value = PassProperties(oldPass=user, newPass1=zaq1, newPass2=zaq1)
             errors = []
          Attribute = msg
              value = Hasło zostało zmienione
  
  FlashMap:
         Attributes = null
  
  MockHttpServletResponse:
             Status = 200
      Error message = null
            Headers = [Content-Language:&quot;en&quot;, Content-Type:&quot;text/html;charset=UTF-8&quot;, X-Content-Type-Options:&quot;nosniff&quot;, X-XSS-Protection:&quot;1; mode=block&quot;, Cache-Control:&quot;no-cache, no-store, max-age=0, must-revalidate&quot;, Pragma:&quot;no-cache&quot;, Expires:&quot;0&quot;, X-Frame-Options:&quot;DENY&quot;]
       Content type = text/html;charset=UTF-8
               Body = &lt;!DOCTYPE html&gt;
  
      Forwarded URL = null
     Redirected URL = null
            Cookies = []
  -------------------------------
  PassProperties(oldPass=user, newPass1=zaq1, newPass2=zaq1)
  Hasło zostało zmienione
  -------------------------------
  
  2020-08-22 18:01:49.655  INFO 12964 --- [extShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
  2020-08-22 18:01:49.665  INFO 12964 --- [extShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.
  
  Process finished with exit code 0
  </code></pre>
  <h1>Edit 2</h1>
  <p>I know that I can override &quot;passProperties&quot; with new PassProperties in @PostMapping to erase previous values but I am really confused why it happens.</p>