Building REST APIs with Spring Boot

Path Variables & Request Parameters

18 min Lesson 3 of 13

Path Variables & Request Parameters

Every REST API needs a way to let callers identify which resource they want and how they want it filtered or shaped. Spring MVC gives you two clean mechanisms for this: path variables (segments embedded in the URI itself) and request parameters (key-value pairs in the query string). Knowing when to use each — and how to use both correctly — is the foundation of a well-designed REST API.

Path Variables with @PathVariable

A path variable is a named placeholder inside the URL template, wrapped in curly braces. When a request arrives, Spring extracts the actual value at that position and injects it into the method parameter annotated with @PathVariable.

import org.springframework.web.bind.annotation.*; import org.springframework.http.ResponseEntity; @RestController @RequestMapping("/api/products") public class ProductController { private final ProductService productService; public ProductController(ProductService productService) { this.productService = productService; } // GET /api/products/42 @GetMapping("/{id}") public ResponseEntity<Product> getById(@PathVariable Long id) { return productService.findById(id) .map(ResponseEntity::ok) .orElse(ResponseEntity.notFound().build()); } }

By default the variable name in the template must match the parameter name. If they differ (e.g. in compiled code that strips parameter names), supply the name explicitly: @PathVariable("id") Long productId.

Multiple Path Variables

You can have as many path segments as the resource hierarchy requires. A common pattern is a nested resource: an order belonging to a specific customer.

// GET /api/customers/7/orders/103 @GetMapping("/{customerId}/orders/{orderId}") public ResponseEntity<Order> getOrder( @PathVariable Long customerId, @PathVariable Long orderId) { Order order = orderService.findByCustomerAndId(customerId, orderId); return ResponseEntity.ok(order); }
Path variables encode identity, not filter criteria. A URL like /products/42 unambiguously names one resource. This is why REST APIs use path variables for IDs and primary lookup keys.

Optional Path Variables

You can mark a path variable optional with required = false and pair it with a java.util.Optional wrapper or a @Nullable-typed default. In practice this is rare for path segments — if a segment can be absent, a separate mapping is usually cleaner — but it is valid syntax.

@GetMapping({"/{category}", "/"}) public List<Product> list( @PathVariable(required = false) String category) { return category == null ? productService.findAll() : productService.findByCategory(category); }

Request Parameters with @RequestParam

Request parameters live in the query string: ?page=2&size=20&sort=name. They are ideal for optional filters, pagination, sorting, and search keywords — anything that narrows or shapes the result set rather than identifying a specific resource.

// GET /api/products?category=electronics&page=0&size=10 @GetMapping public Page<Product> list( @RequestParam(required = false) String category, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size) { Pageable pageable = PageRequest.of(page, size); return category == null ? productService.findAll(pageable) : productService.findByCategory(category, pageable); }

Key attributes of @RequestParam:

  • required — defaults to true; Spring returns 400 if the parameter is missing and you do not supply a default.
  • defaultValue — a string that is type-converted to the parameter type; implies required = false.
  • name (or value) — use when the query-string key differs from the Java parameter name.
Always set a defaultValue for pagination parameters. A caller who omits page or size would otherwise get a 400 error. Defaulting to page 0 and a reasonable page size (10–25) is the standard REST convention.

Multi-Value Parameters

A query string can carry the same key more than once: ?tag=java&tag=spring. Bind it to a List and Spring collects all values automatically.

// GET /api/products?tag=java&tag=spring @GetMapping("/search") public List<Product> searchByTags( @RequestParam List<String> tag) { return productService.findByTags(tag); }

Type Conversion

Spring's ConversionService converts the raw string value to the declared parameter type automatically. Primitives (int, long, boolean), wrapper types, UUID, LocalDate, and enums all work out of the box. If conversion fails Spring returns a 400 Bad Request with a descriptive message — you do not need to write parsing code yourself.

// GET /api/orders?status=SHIPPED&since=2024-01-01 @GetMapping("/orders") public List<Order> orders( @RequestParam OrderStatus status, @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate since) { return orderService.findByStatusSince(status, since); }
Do not bind unbounded free-text params directly to database queries without validation. Even though Spring handles type safety, a caller can pass arbitrarily large strings. Validate lengths and formats — either with Bean Validation annotations (@RequestParam @Size(max=100) String q) or in the service layer — before passing values to persistence code.

Path Variable vs Request Parameter — The Design Rule

A practical rule of thumb used by experienced REST API designers:

  • Use a path variable when the value uniquely identifies a resource or a specific level in a resource hierarchy: /users/99, /users/99/posts/12.
  • Use a request parameter when the value filters, sorts, paginates, or otherwise modifies the response without changing which resource collection is addressed: /users?role=admin&page=2.

This maps neatly onto REST semantics: the path names the resource; the query string shapes the representation.

Putting It Together — A Realistic Endpoint

// GET /api/products/42/reviews?rating=5&page=0&size=5 @GetMapping("/{productId}/reviews") public Page<Review> getReviews( @PathVariable Long productId, @RequestParam(required = false) Integer rating, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "5") int size) { Pageable pageable = PageRequest.of(page, size); return reviewService.findByProduct(productId, rating, pageable); }

This single method cleanly separates identity (productId in the path) from filtering (rating) and pagination (page, size) — exactly the pattern you will see in mature REST APIs like GitHub's, Stripe's, and the Twitter/X API.

Summary

Use @PathVariable for values that identify a resource in the URI hierarchy and @RequestParam for optional query-string values that filter or shape the response. Both support automatic type conversion. Always supply defaultValue for optional parameters to avoid unexpected 400 responses and keep your API forgiving for clients.