title: SpringBoot urlencodes plus sign in query parameters in an inconsistent manner
body: <p>I have a simple echo controller</p>
  
  <pre><code>@RestController
  public class EchoController {
      @GetMapping(path = "/param", produces = MediaType.TEXT_PLAIN_VALUE)
      String echoParam(@RequestParam("p") String paramValue) {
          return paramValue;
      }
  
      @GetMapping(path = "/path-variable/{val}", produces = MediaType.TEXT_PLAIN_VALUE)
      String echoPathVariable(@PathVariable("val") String val) {
          return val;
      }
  }
  </code></pre>
  
  <p>One of its methods echoes the value of a parameter it was presented; the second does the same with a value provided via URI.</p>
  
  <p>I have the following tests:</p>
  
  <pre><code>@Autowired
  private WebTestClient webTestClient;
  
  @Test
  public void rawPlus_inQueryParam() {
      String value = "1+1";
  
      String response = getValueEchoedThroughQueryParam(value);
  
      assertThat(response, is(equalTo(value)));
  }
  
  @Test
  public void urlencodedPlus_inQueryParam() {
      String value = "1%2B1";
  
      String response = getValueEchoedThroughQueryParam(value);
  
      assertThat(response, is(equalTo(value)));
  }
  
  private String getValueEchoedThroughQueryParam(String value) {
      return webTestClient.get()
              .uri(builder -&gt; {
                  return builder
                          .path("/param")
                          .queryParam("p", value)
                          .build();
              })
              .exchange()
              .expectStatus().is2xxSuccessful()
              .expectBody(String.class)
              .returnResult()
              .getResponseBody();
  }
  </code></pre>
  
  <p>Both tests just send a string via the query parameter, read the response and assert that the content was echoed correctly.</p>
  
  <p>First test fails:</p>
  
  <pre><code>java.lang.AssertionError: 
  Expected: is "1+1"
       but: was "1 1"
  </code></pre>
  
  <p>Second test passes.</p>
  
  <p>Looks like in the second test, the <code>WebTestClient</code> url-encodes the value (actually, it just url-encodes the percent character), then the web-server url-decodes it, and everything is ok. But in the first test, the client does <strong>not</strong> url-encode the plus character, but the server <strong>does</strong> url-decode it, hence it gets a space character.</p>
  
  <p>This looks like an inconsistency. I doubt I could do something stupid to cause it because everything works in the default mode; actually, the controller I show here is almost the only code the application has (Application class itself is default, I did not touch it after it was generated).</p>
  
  <p>Just to compare: when passing the same data in URI, everything works correctly. The following tests pass:</p>
  
  <pre><code>@Test
  public void rawPlus_inPathVariable() {
      String value = "1+1";
  
      String response = getValueEchoedThroughPathVariable(value);
  
      assertThat(response, is(equalTo(value)));
  }
  
  @Test
  public void urlencodedPlus_inPathVariable() {
      String value = "1%2B1";
  
      String response = getValueEchoedThroughPathVariable(value);
  
      assertThat(response, is(equalTo(value)));
  }
  
  private String getValueEchoedThroughPathVariable(String value) {
      return webTestClient.get()
              .uri("/path-variable/" + value)
              .exchange()
              .expectStatus().is2xxSuccessful()
              .expectBody(String.class)
              .returnResult()
              .getResponseBody();
  }
  </code></pre>
  
  <p><strong>The questions are</strong>:</p>
  
  <ol>
  <li>Is this a bug in Spring Boot (or one of the components it uses)?</li>
  <li>If not, did I do something wrong?</li>
  <li>And the practical question: how can you pass a query parameter value that contains plus character in tests written against <code>WebTestClient</code>? If I don't url-encode it manually, it gets url-decoded by the web-server; if I do, it gets url-encoded second time, so the server gets (after url-decoding) an url-decoded version.</li>
  </ol>
  
  <p>Spring Boot version is <code>2.1.4.RELEASE</code> (the most recent at the moment of writing).</p>
  
  <p>POM follows:</p>
  
  <pre><code>&lt;?xml version="1.0" encoding="UTF-8"?&gt;
  &lt;project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"&gt;
      &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;
      &lt;parent&gt;
          &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
          &lt;artifactId&gt;spring-boot-starter-parent&lt;/artifactId&gt;
          &lt;version&gt;2.1.4.RELEASE&lt;/version&gt;
          &lt;relativePath /&gt; &lt;!-- lookup parent from repository --&gt;
      &lt;/parent&gt;
      &lt;groupId&gt;com.example&lt;/groupId&gt;
      &lt;artifactId&gt;springboot-plus-in-query-string&lt;/artifactId&gt;
      &lt;version&gt;0.0.1-SNAPSHOT&lt;/version&gt;
      &lt;name&gt;springboot-plus-in-query-string&lt;/name&gt;
      &lt;description&gt;Demo project for Spring Boot&lt;/description&gt;
  
      &lt;properties&gt;
          &lt;java.version&gt;1.8&lt;/java.version&gt;
      &lt;/properties&gt;
  
      &lt;dependencies&gt;
          &lt;dependency&gt;
              &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
              &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;
          &lt;/dependency&gt;
  
          &lt;dependency&gt;
              &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
              &lt;artifactId&gt;spring-boot-starter-test&lt;/artifactId&gt;
              &lt;scope&gt;test&lt;/scope&gt;
          &lt;/dependency&gt;
  
          &lt;dependency&gt;
              &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
              &lt;artifactId&gt;spring-boot-starter-webflux&lt;/artifactId&gt;
              &lt;scope&gt;test&lt;/scope&gt;
          &lt;/dependency&gt;
      &lt;/dependencies&gt;
  
      &lt;build&gt;
          &lt;plugins&gt;
              &lt;plugin&gt;
                  &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
                  &lt;artifactId&gt;spring-boot-maven-plugin&lt;/artifactId&gt;
              &lt;/plugin&gt;
          &lt;/plugins&gt;
      &lt;/build&gt;
  
  &lt;/project&gt;
  </code></pre>
  
  <p>The project is available at GitHub: <a href="https://github.com/rpuch/springboot-plus-in-query-string" rel="nofollow noreferrer">https://github.com/rpuch/springboot-plus-in-query-string</a></p>
  
  <p>Just run the tests (<code>mvn clean test</code>).</p>