The @PathVariable annotation in Spring extracts values from the URI path and binds them to method parameters in a Spring MVC
controller. It is commonly used with @GetMapping, @PostMapping, @PutMapping, and @DeleteMapping to
capture path variables from the URI. These annotations map HTTP requests to specific handler methods in a controller. They are part of the Spring Web
module and are commonly used to define the routes for different HTTP operations in a RESTful API.
If a method has a path template containing a placeholder, like "/api/resource/{id}", and there’s no @PathVariable annotation on a
method parameter to capture the id path variable, Spring will disregard the id variable.
Path variables can also be automatically bound to class or record properties when used as method parameters (without @PathVariable).
For Spring Web versions prior to 5.3, the @ModelAttribute annotation is required on the parameter to enable binding to class properties.
Starting with Spring Web 5.3, this binding happens automatically without requiring @ModelAttribute. For classes, Spring binds path
variables to properties that have setter methods (including Lombok-generated setters). For records (Spring Web 5.3+), Spring binds path variables to
record components through their accessor methods. The @BindParam annotation can be used on record components to specify custom binding
names.
This rule will raise an issue if a method has a path template with a placeholder, but no corresponding @PathVariable, matching class
property, or matching record component, or vice-versa.
@GetMapping("/api/resource/{id}")
public ResponseEntity<String> getResourceById(Long id) { // Noncompliant - The 'id' parameter will not be automatically populated with the path variable value
return ResponseEntity.ok("Fetching resource with ID: " + id);
}
@GetMapping("/api/asset/")
public ResponseEntity<String> getAssetById(@PathVariable Long id) { // Noncompliant - The 'id' parameter does not have a corresponding placeholder
return ResponseEntity.ok("Fetching asset with ID: " + id);
}
@GetMapping("/api/resource/{id}")
public ResponseEntity<String> getResourceById(@PathVariable Long id) { // Compliant
return ResponseEntity.ok("Fetching resource with ID: " + id);
}
@GetMapping("/api/asset/{id}")
public ResponseEntity<String> getAssetById(@PathVariable Long id) {
return ResponseEntity.ok("Fetching asset with ID: " + id);
}
// Spring Web < 5.3: Class properties require @ModelAttribute
@Data
class UserRequest {
private String userId;
private String accountId;
}
@GetMapping("/api/user/{userId}/{accountId}")
public ResponseEntity<String> getUser(UserRequest request) { // Noncompliant - @ModelAttribute annotation is required for Spring Web < 5.3
return ResponseEntity.ok("User: " + request.getUserId());
}
// Spring Web < 5.3: Class properties require @ModelAttribute
@Data
class UserRequest {
private String userId;
private String accountId;
}
@GetMapping("/api/user/{userId}/{accountId}")
public ResponseEntity<String> getUser(@ModelAttribute UserRequest request) { // Compliant - @ModelAttribute enables binding to class properties
return ResponseEntity.ok("User: " + request.getUserId());
}
// Spring Web 5.3+: Class properties
@Data
class UserRequest {
private String userId;
private String accountId;
}
@GetMapping("/api/user/{userId}/{companyId}")
public ResponseEntity<String> getUser(UserRequest request) { // Noncompliant - 'companyId' path variable doesn't match any property with a setter
return ResponseEntity.ok("User: " + request.getUserId());
}
// Spring Web 5.3+: Class properties
@Data
class UserRequest {
private String userId;
private String accountId;
}
@GetMapping("/api/user/{userId}/{accountId}")
public ResponseEntity<String> getUser(UserRequest request) { // Compliant - path variables match properties with setters
return ResponseEntity.ok("User: " + request.getUserId());
}
// Spring Web 5.3+: Record components
record OrderRequest(String orderId, String customerId) {}
@GetMapping("/api/order/{order-id}/{customer-id}")
public ResponseEntity<String> getOrder(OrderRequest request) { // Noncompliant - Record component names don't match path variables
return ResponseEntity.ok("Order: " + request.orderId());
}
// Spring Web 5.3+: Record components
record OrderRequest(@BindParam("order-id") String orderId, @BindParam("customer-id") String customerId) {}
@GetMapping("/api/order/{order-id}/{customer-id}")
public ResponseEntity<String> getOrder(OrderRequest request) { // Compliant - @BindParam maps component names to path variables
return ResponseEntity.ok("Order: " + request.orderId());
}