Welcome! In this lesson, we'll dive into Handling Path Variables and Query Parameters in Spring Boot. You've already learned how to create REST endpoints and return JSON responses. Now, we’ll extend that knowledge by exploring how to handle path variables and query parameters in your APIs.
Path variables and query parameters are crucial for passing information and filtering data in RESTful APIs. These tools will make your endpoints more dynamic and powerful. Let’s get started!
In previous lessons, we used hardcoded endpoints like /recipes/american-sandwich
. As your application grows and you have a database with thousands of recipes, defining a separate endpoint for each recipe becomes impractical. Instead, you can use path variables, such as: /recipes/{recipeId}
. Here, recipeId
is a dynamic part of the URL that gets passed to your methods. For example:
- When someone requests
/recipes/123
, therecipeId
variable will take the value123
. - When someone requests
/recipes/pizza-pepperoni
, therecipeId
variable will take the valuepizza-pepperoni
.
You can also have paths with multiple path variables. For instance, by introducing recipe categories, you can have the following paths:
/categories/{recipeCategory}
/categories/{recipeCategory}/recipes/{recipeId}
This hierarchical structure makes your API more organized and reflective of your domain model.
Imagine you have a collection of recipes, and you want to retrieve a specific recipe by its unique ID. Here’s how you can implement this in Spring Boot Kotlin:
Kotlin1@GetMapping("/recipes/{recipeId}") 2fun getRecipeById(@PathVariable recipeId: Long): Recipe { 3 return recipeRepository.findById(recipeId) 4 ?: throw IllegalArgumentException("Recipe not found") 5}
Here’s what’s happening:
@GetMapping("/recipes/{recipeId})
: Maps HTTP GET requests to/recipes/{recipeId}
.{recipeId}
is a path variable.@PathVariable recipeId: Long
: Binds the path variablerecipeId
from the URL to the method parameter.- The method interacts with
recipeRepository
to find a recipe by its ID. - If no matching recipe is found, it throws an
IllegalArgumentException
.
Path variables make your URLs more dynamic and informative.
In the previous example, we assumed the template path variable and method parameter names were the same. But if they're different, you can specify the name in the @PathVariable
annotation:
Kotlin1@GetMapping("/recipes/{recipeId}") 2fun getRecipe(@PathVariable("recipeId") id: Long): Recipe { 3 return recipeRepository.findById(id) 4 ?: throw IllegalArgumentException("Recipe not found") 5}
Here’s what’s happening:
@GetMapping("/recipes/{recipeId})
: Maps HTTP GET requests to/recipes/{recipeId}
.@PathVariable("recipeId") id: Long
: Binds the path variablerecipeId
from the URL to the method parameterid
.- The rest works as before.
This approach provides clarity and flexibility when the names differ.
Query parameters pass additional information to endpoints, typically for filtering or sorting data. They are added to the URL after a question mark and help narrow down search results. Here are some examples:
/recipes?diet=vegan
: Filters recipes by diet type, showing only vegan recipes./recipes?diet=vegan&complexity=easy
: Filters recipes by diet type and complexity, showing only easy vegan recipes./recipes?diet=vegan&complexity=easy&prepTime=30
: Filters recipes by diet type, complexity, and prep time, showing only easy vegan recipes that take 30 minutes or less to prepare.
Suppose you want to filter recipes by type (e.g., vegan, vegetarian). Here’s how to implement this:
Kotlin1@GetMapping("/recipes") 2fun getRecipesByType(@RequestParam type: String): List<Recipe> { 3 return recipeService.getRecipesByType(type) 4}
Here's what’s happening:
@GetMapping("/recipes")
: Maps HTTP GET requests to/recipes
.@RequestParam type: String
: Binds the query parametertype
to the method parameter.- The method calls
getRecipesByType
inrecipeService
to get recipes filtered by type.
Query parameters enable more detailed and specific requests to your API.
Sometimes, you may want to allow a query parameter to be optional. This means that the request can still be processed even if the query parameter is not provided. You can make a query parameter optional by using nullable types:
Kotlin1@GetMapping("/recipes") 2fun getRecipesByType(@RequestParam type: String?): List<Recipe> { 3 return recipeService.getRecipesByType(type) 4}
Here’s what’s happening:
@RequestParam type: String?
: Binds the query parametertype
to a nullable type, making it clear that the parameter may or may not be present.- The method
getRecipesByType
will handle the case wheretype
is null appropriately.
Using nullable types in Kotlin provides a clean and explicit way to handle optional query parameters, enhancing code readability and maintainability.
When the query parameter name differs from the method parameter name, you can explicitly name the query parameter. Here’s how:
Kotlin1@GetMapping("/recipes") 2fun getRecipes(@RequestParam("type") recipeType: String?): List<Recipe> { 3 return recipeService.getRecipesByType(recipeType) 4}
Here's what’s happening:
@GetMapping("/recipes")
: Maps HTTP GET requests to/recipes
.@RequestParam("type") recipeType: String?
: Binds the query parametertype
from the URL to the method parameterrecipeType
.
This approach provides flexibility when names differ.
You can combine path variables and query parameters in a single endpoint. This is useful when you need to identify a specific resource category and apply some filters or additional criteria.
Here’s an endpoint that retrieves recipes by category and allows filtering for a specific dietary preference:
Kotlin1@GetMapping("/category/{recipeCategory}") 2fun getRecipesByCategoryAndDietaryPreference( 3 @PathVariable recipeCategory: String, 4 @RequestParam dietaryPreference: String? 5): List<Recipe> { 6 val recipes = recipeRepository.findByCategory(recipeCategory) 7 8 return dietaryPreference?.let { preference -> 9 recipes.filter { recipe -> recipe.dietaryPreference.equals(preference, ignoreCase = true) } 10 } ?: recipes 11}
This example shows how combining path variables and query parameters can create powerful and flexible endpoints. For instance, a request to /category/Italian?dietaryPreference=vegan
will retrieve all recipes within the "Italian" category and filter them for those labeled as "vegan".
Here’s what’s happening in the code:
@GetMapping("/category/{recipeCategory}")
: Maps HTTP GET requests to/category/{recipeCategory}
.@PathVariable recipeCategory: String
: Binds the path variablerecipeCategory
from the URL to the method parameter.@RequestParam dietaryPreference: String?
: Binds the query parameterdietaryPreference
, which is optional, to the method parameter.- The method retrieves recipes by category from
recipeRepository
. - If
dietaryPreference
is provided, it filters the recipes by matching the dietary preference. If not, it returns all recipes within the specified category.
In this lesson, we've explored how to handle path variables and query parameters in Spring Boot. You’ve learned:
- The importance of path variables and how to use them
- How query parameters can make your endpoints more versatile
- Using named path variables and query parameters for clarity and flexibility
- Combining path variables and query parameters for dynamic requests
In the upcoming exercises, you’ll practice these concepts hands-on. Understanding how to use path variables and query parameters effectively will make your APIs more dynamic and efficient. Good luck!