@PathVariable

Binds a {placeholder} from the request path, such as an id, to a method parameter with type conversion.

Signature
@PathVariable("id") long id
  • Added in Spring Boot Spring Framework 3.0

Parameters

ParameterTypeDescription
$valueoptionalString= ""Alias for name.
$nameoptionalString= ""The template variable to bind. Defaults to the parameter name, which requires compiling with -parameters (Spring Boot's build plugins do this).
$requiredoptionalboolean= trueWhether 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 path

Patterns

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/42 is 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 = false to make /api/orders/ match /api/orders/{id}. It does not; map both paths on the method.
  • Encoded slashes. %2F inside a segment is rejected by most servlet containers and firewalls. Do not design ids that contain slashes.

Example

Example · java
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) {}

@RestController
@RequestMapping("/api/orders")
public class OrderLineController {

    // GET /api/orders/42/lines/3
    // "42" is converted to long and "3" to int. "abc" gives 400.
    @GetMapping("/{orderId}/lines/{line}")
    LineItem line(@PathVariable long orderId, @PathVariable int line) {
        return new LineItem(orderId, line, "KET-01");
    }

    // The regex keeps "/api/orders/export/lines" free for another mapping.
    @GetMapping("/{orderId:\\d+}/lines")
    List<LineItem> lines(@PathVariable 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.

Example · java
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.

Example · java
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.

Example · java
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

  • Be the first to comment on this lesson.