Spring @PathVariable Annotation
In Spring Web MVC, we can use the @PathVariable
annotation to access the captured URI variables.
Table of contents:
- 1. Basic Mapping
- 2. Basic Mapping – Different Name
- 3. Basic Mapping – Class and method levels
- 4. Multiple Captured URI Variables
- 5. Multiple Captured URI Variables – Map Version
- 6. Regex Mapping
- 7. @PathVariable variables are required by default
- 8. java.util.Optional
- 9. FAQs
- 10. Download Source Code
- 11. References
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;
}
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;
}
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.
@RestController
@RequestMapping("/path2/api/{id}")
public class PathVariableController2 {
@GetMapping("/page/{title}")
public String getPageById(@PathVariable String id, @PathVariable String title) {
return id + title;
}
}
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;
}
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";
}
}
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;
}
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;
}
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!";
}
}
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!");
}
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";
}
}
Hi, This is nice blog, Thank you for sharing…