Main Tutorials

Spring @PathVariable Annotation

In Spring Web MVC, we can use the @PathVariable annotation to access the captured URI variables.

Table of contents:

P.S Tested with Spring 5.

1. Basic Mapping

For the mapping to work corectly, the name of the capture URI variable {id} and @PathVariable member parameter String id must be the same.


{id} -> @PathVariable String id -> OK
{id} -> @PathVariable String name -> Error , id and name is different

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

  @GetMapping("/api/page/{id}")
  public String getPageById(@PathVariable String id) {
      return id;
  }
Terminal

curl http://localhost:8080/api/page/1

1

Unit Test


  @Test
  public void testGetPageById() throws Exception {

      MvcResult result = this.mockMvc.perform(get("/path//api/page/1"))
            .andExpect(status().isOk())
            .andExpect(content().string("1"))
            .andReturn();

  }

2. Basic Mapping – Different Name

We also can explicitly access the capture URI variable by passing the argument into the @PathVariable annotation.


{id} -> @PathVariable String id -> OK
{id} -> @PathVariable String name -> Error , id and name is different
{id} -> @PathVariable("id") String name -> ok

  @GetMapping("/api/title/{title}")
  public String getPageByName(@PathVariable("title") String name) {
      return name;
  }
Terminal

curl http://localhost:8080/path/api/title/hello-world

hello-world

Unit Test


  @Test
  public void getPageByName() throws Exception {

      MvcResult result = this.mockMvc.perform(get("/path/api/title/hello-world"))
              .andExpect(status().isOk())
              .andExpect(content().string("hello-world"))
              .andReturn();

  }

3. Basic Mapping – Class and method levels

The below example shows how to use @PathVariable to access the captured URI variables at the class and method levels.

PathVariableController2.java

@RestController
@RequestMapping("/path2/api/{id}")
public class PathVariableController2 {

  @GetMapping("/page/{title}")
  public String getPageById(@PathVariable String id, @PathVariable String title) {
      return id + title;
  }

}
Terminal

curl http://localhost:8080/path2/api/99/page/hello-world/

99hello-world

Unit Test


  @Test
  public void getPageUsingClassAndMethod() throws Exception {

      this.mockMvc.perform(get("/path2/api/99/page/hello-world/"))
            .andExpect(status().isOk())
            .andExpect(content().string("99hello-world"))
            .andReturn();

  }

4. Multiple Captured URI Variables

We can define multiple @PathVariable annotations to access the captured URI variables.


  @GetMapping("/api/multi/{tag}/{name}")
  public String getPageByTagAndName(@PathVariable String tag, @PathVariable String name) {
      return tag + ":" + name;
  }
Terminal

curl http://localhost:8080/path/api/multi/spring-mvc/hello-world

spring-mvc:hello-world

Unit Test


  @Test
  public void getPageByTagAndName() throws Exception {

      MvcResult result = this.mockMvc.perform(get("/path/api/multi/spring-mvc/hello-world"))
              .andExpect(status().isOk())
              .andExpect(content().string("spring-mvc:hello-world"))
              .andReturn();

  }

5. Multiple Captured URI Variables – Map Version

The @PathVariable annotation supports java.util.Map data type.


  @GetMapping("/api/multi/{tag}/and/{name}")
  public String getPageByTagAndNameMapVersion(@PathVariable Map<String, String> map) {
      String tag = map.get("tag");
      String name = map.get("name");
      if (tag != null && name != null) {
          return tag + ":" + name;
      } else {
          return "Tag or name are missing";
      }
  }
Terminal

curl http://localhost:8080/path/api/multi/spring-mvc/and/hello-world

spring-mvc:hello-world

  @Test
  public void getPageByTagAndNameMapVersion() throws Exception {

      MvcResult result = this.mockMvc.perform(get("/path/api/multi/spring-mvc/and/hello-world"))
              .andExpect(status().isOk())
              .andExpect(content().string("spring-mvc:hello-world"))
              .andReturn();

  }

6. Regex Mapping

The @PathVariable supports regex {varName:regex} to access the capture URI variables.


  // From docs.spring.io, modify to support version double digits and .RELEASE as optional
  // spring-webmvc-5.3.22.jar, spring-webmvc-5.2.22.RELEASE.jar
  @GetMapping("/api/get/{name:[a-z-]+}-{version:\\d{1,2}\\.\\d{1,2}\\.\\d{1,2}(?:\\.RELEASE)?}{ext:\\.[a-z]+}")
  public String getJarFile(@PathVariable String name,
                           @PathVariable String version,
                           @PathVariable String ext) {
      return name + "-" + version + ext;
  }
Terminal

curl http://localhost:8080/path/api/get/spring-webmvc-5.3.22.jar

spring-webmvc-5.3.22.jar

curl http://localhost:8080/path/api/get/spring-webmvc-5.2.22.RELEASE.jar

spring-webmvc-5.2.22.RELEASE.jar

Unit Test


  @Test
  public void getJarFile() throws Exception {

      String jar = "spring-webmvc-5.3.22.jar";

      MvcResult result = this.mockMvc.perform(get("/path/api/get/" + jar))
              .andExpect(status().isOk())
              .andExpect(content().string(jar))
              .andReturn();

      String jar2 = "spring-webmvc-5.2.22.RELEASE.jar";

      MvcResult result2 = this.mockMvc.perform(get("/path/api/get/" + jar2))
              .andExpect(status().isOk())
              .andExpect(content().string(jar2))
              .andReturn();
  }

7. @PathVariable variables are required by default

Below @GetMapping method handles multiple request paths, but the @PathVariable are required by default, which means if we access the request path /api2/page/, Spring will return an HTTP 500 server error code and also error message like missing URI template variable.


  @GetMapping(value = {"/api2/page/", "/api2/page/{id}"})
  public String getPageByApi2(@PathVariable String id) {
      return id;
  }
Terminal

curl http://localhost:8080/path/api2/page/

HTTP 500
Missing URI template variable 'id' for method parameter of type String
SERVLET:	dispatcher

curl http://localhost:8080/path/api2/page/1

1

Unit Test


  @Test
  public void getPageByApi2() throws Exception {

      this.mockMvc.perform(get("/path/api2/page/"))
              .andExpect(status().is5xxServerError())
              .andReturn();

      this.mockMvc.perform(get("/path/api2/page/1"))
              .andExpect(status().isOk())
              .andExpect(content().string("1"))
              .andReturn();

  }

7.1 Set @PathVariable as optional

We can use @PathVariable(required = false) to make it optional.


  @GetMapping(value = {"/api3/page/", "/api3/page/{id}"})
  public String getPageByApi3(@PathVariable(required = false) String id) {

      if (id != null) {
          return id;
      } else {
          return "id is required!";
      }

  }
Terminal

curl http://localhost:8080/path/api3/page/

id is required!

curl http://localhost:8080/path/api3/page/1

1

Unit Test


  @Test
  public void getPageByApi3() throws Exception {

      this.mockMvc.perform(get("/path/api3/page/"))
              .andExpect(status().isOk())
              .andExpect(content().string("id is required!"))
              .andReturn();

      this.mockMvc.perform(get("/path/api3/page/1"))
              .andExpect(status().isOk())
              .andExpect(content().string("1"))
              .andReturn();

  }

8. java.util.Optional

The @PathVariable annotation supports the Java 8 Optional type.


  @GetMapping(value = {"/api4/page/", "/api4/page/{id}"})
  public String getPageByApi4(@PathVariable Optional<String> id) {

      if (id.isPresent()) {
          return id.get();
      } else {
          return "id is required!";
      }

      // or one line
      // return id.orElse("id is required!");

  }
Terminal

curl http://localhost:8080/path/api4/page/

id is required!

curl http://localhost:8080/path/api4/page/1

1

Unit Test


  @Test
  public void getPageByApi4() throws Exception {

      this.mockMvc.perform(get("/path/api4/page/"))
              .andExpect(status().isOk())
              .andExpect(content().string("id is required!"))
              .andReturn();

      this.mockMvc.perform(get("/path/api4/page/1"))
              .andExpect(status().isOk())
              .andExpect(content().string("1"))
              .andReturn();


  }

9. FAQs

Some FAQs about the @PathVariable annotation.

9.1 Why is the value truncated after the last (.)dot?


  @GetMapping("/hello/{name}")
  public String whereIsDot(@PathVariable("name") String name) {
      return name;
  }

By default, Spring considers the value after the last dot or period (.) as a file extension and truncates them automatically. For example, in the above example, if we access the request path "/hello/abc.jar", Spring will return only "abc"; Spring will truncate or deletes the words .jar.

To fix it, try regex mapping {name:.+}.


  @GetMapping("/hello/{name:.+}")
  public String whereIsDotFixed(@PathVariable("name") String name) {
      return name;
  }

9.2 Can we set a default value for the @PathVariable

If null or empty, we can simply return something as default.

java.util.Optional


  @GetMapping(value = {"/api/page/", "/api/page/{id}"})
  public String getPageByApi4(@PathVariable Optional<String> id) {
      // default id is 99
      return id.orElse("99");
  }

A simple if else will do.


  @GetMapping(value = {"/api/page/", "/api/page/{id}"})
  public String getPageByApi3(@PathVariable(required = false) String id) {

      if (id != null) {
          return id;
      } else {
          return "99";
      }

  }

10. Download Source Code

$ git clone https://github.com/mkyong/spring-mvc/

$ cd spring-mvc-basic

$ mvn clean jetty:run

11. References

About Author

author image
Founder of Mkyong.com, love Java and open source stuff. Follow him on Twitter. If you like my tutorials, consider make a donation to these charities.

Comments

Subscribe
Notify of
1 Comment
Most Voted
Newest Oldest
Inline Feedbacks
View all comments
kosmik technologies
1 year ago

Hi, This is nice blog, Thank you for sharing…