@PathVariable
Binds a {placeholder} from the request path, such as an id, to a method parameter with type conversion.
@PathVariable("id") long id- Added in Spring Boot Spring Framework 3.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$valueoptional | String= "" | Alias for name. |
$nameoptional | String= "" | The template variable to bind. Defaults to the parameter name, which requires compiling with -parameters (Spring Boot's build plugins do this). |
$requiredoptional | boolean= true | Whether the variable must be present. Set false only when several mappings share the method and one lacks the variable; an Optional parameter has the same effect. |
Return value
n/aBinds a URI template variable to the parameter, converting the text to the parameter type (long, UUID, enum and so on).
Errors
A value that cannot be converted gives 400 via MethodArgumentTypeMismatchException.
@PathVariable reads part of the URL path. The mapping declares a template such as /api/orders/{orderId}, and the parameter with the matching name receives the text from the request, converted to its type. Use path variables for things that identify a resource: ids, slugs, nested ids. Filters, sorting and paging belong in query parameters.
Names and conversion
The variable name defaults to the Java parameter name. That relies on the class being compiled with -parameters; Spring Boot's Maven and Gradle plugins enable it. A build that bypasses them fails at runtime with a message that the parameter name is not available. Set the name explicitly (@PathVariable("id")) when it differs from the Java name or when the template uses characters Java does not allow.
Conversion uses Spring's conversion service: long, UUID, enums, LocalDate and any type with a registered converter all work. Text that does not convert gives 400 Bad Request before your method runs.
@GetMapping("/{orderId}/lines/{line}")
LineItem line(@PathVariable long orderId, @PathVariable int line)
@GetMapping("/{id:\\d+}") // only digits match
@GetMapping("/files/{*path}") // rest of the pathPatterns
A regular expression after a colon restricts what a segment matches: {id:\d+} only matches digits. That is useful when a literal segment such as /export sits beside /{id}; Spring already prefers the literal, but the regex makes the intent explicit and turns /api/orders/abc into a 404 instead of a conversion error. {*path} captures the rest of the URL, slashes included.
Mistakes people make
- Trusting the id.
/api/invoices/42is trivially editable. Check that the current user may see invoice 42, or you have an insecure direct object reference. - Using sequential ids publicly when that leaks business volume. UUIDs or opaque slugs avoid it.
- Expecting
required = falseto make/api/orders/match/api/orders/{id}. It does not; map both paths on the method. - Encoded slashes.
%2Finside a segment is rejected by most servlet containers and firewalls. Do not design ids that contain slashes.
Example
import java.util.List;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
record LineItem(long orderId, int line, String sku) {}
("/api/orders")
public class OrderLineController {
// GET /api/orders/42/lines/3
// "42" is converted to long and "3" to int. "abc" gives 400.
("/{orderId}/lines/{line}")
LineItem line( long orderId, int line) {
return new LineItem(orderId, line, "KET-01");
}
// The regex keeps "/api/orders/export/lines" free for another mapping.
("/{orderId:\\d+}/lines")
List<LineItem> lines( long orderId) {
return List.of(new LineItem(orderId, 1, "KET-01"));
}
}
When to use it
- Reading one order by its numeric id
- Addressing a nested resource such as a line within an order
- Serving files from object storage by their key path
Frequently asked questions
Can I validate a path variable with @Min or @Pattern?
Yes. Put the constraint on the parameter and add the validation starter; Spring's built-in method validation rejects bad values with a 400.
Why do I get 'Name for argument not specified'?
The class was compiled without -parameters, so Spring cannot read the parameter name. Build through the Spring Boot plugin or set the name on the annotation.
More examples
UUID ids with a clear 400
The UUID converter rejects malformed text with MethodArgumentTypeMismatchException. A local @ExceptionHandler turns it into a problem detail naming the parameter instead of a generic 400.
import java.util.UUID;
import org.springframework.http.HttpStatus;
import org.springframework.http.ProblemDetail;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
record Shipment(UUID id, String carrier) {}
@RestController
public class ShipmentController {
@GetMapping("/api/shipments/{id}")
Shipment shipment(@PathVariable UUID id) {
return new Shipment(id, "Royal Mail");
}
// "/api/shipments/not-a-uuid" fails conversion; say so clearly.
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
ProblemDetail badId(MethodArgumentTypeMismatchException ex) {
return ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST,
"'" + ex.getName() + "' must be a UUID");
}
}
Explicit names and a map of all variables
Template names with hyphens cannot be Java parameter names, so name them explicitly. A Map<String, String> parameter collects every template variable, which suits generic proxies and logging.
import java.util.Map;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TenantController {
// The name attribute is needed when the Java name differs from the template.
@GetMapping("/api/tenants/{tenant-id}/users/{user-id}")
Map<String, String> user(@PathVariable("tenant-id") String tenantId,
@PathVariable("user-id") String userId) {
return Map.of("tenant", tenantId, "user", userId);
}
// A Map parameter receives every template variable at once.
@GetMapping("/api/regions/{region}/stores/{store}")
Map<String, String> store(@PathVariable Map<String, String> vars) {
return vars;
}
}
Capturing the rest of the path
{*path} must be the last element of the pattern. The captured value starts with a slash, so strip it if you join it onto a base directory, and never pass it to the file system without normalising it first.
import java.util.Map;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class FileController {
// {*path} captures the rest of the URL, slashes included.
// GET /api/files/reports/2026/q3.pdf -> path = "/reports/2026/q3.pdf"
@GetMapping("/api/files/{*path}")
Map<String, String> file(@PathVariable String path) {
return Map.of("path", path);
}
}
Discussion