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:
Java1@GetMapping("/recipes/{recipeId}") 2public Recipe getRecipeById(@PathVariable Long recipeId) { 3 return recipeRepository.findById(recipeId) 4 .orElseThrow(() -> new 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 Long recipeId
: 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:
Java1@GetMapping("/recipes/{recipeId}") 2public Recipe getRecipe(@PathVariable("recipeId") Long id) { 3 return recipeRepository.findById(id) 4 .orElseThrow(() -> new IllegalArgumentException("Recipe not found")); 5}
Here’s what’s happening:
@GetMapping("/recipes/{recipeId})
: Maps HTTP GET requests to/recipes/{recipeId}
.@PathVariable("recipeId") Long id
: 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:
Java1@GetMapping("/recipes") 2public List<Recipe> getRecipesByType(@RequestParam String type) { 3 return recipeService.getRecipesByType(type); 4}
Here's what’s happening:
@GetMapping("/recipes")
: Maps HTTP GET requests to/recipes
.@RequestParam String type
: 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. To make a query parameter optional, you can provide a default value or make it nullable:
Java1@GetMapping("/recipes") 2public List<Recipe> getRecipesByType(@RequestParam(required = false) String type) { 3 return recipeService.getRecipesByType(type); 4}
Here’s what’s happening:
@RequestParam(required = false) String type
: Binds the query parametertype
to the method parameter and allows it to be null if not provided.- The method
getRecipesByType
will handle the case wheretype
is null appropriately.
This makes the query parameter optional and your API more flexible.
Another way to make a query parameter optional is by using java.util.Optional
. This approach provides a more explicit way of dealing with the presence or absence of a query parameter:
Java1@GetMapping("/recipes") 2public List<Recipe> getRecipesByType(@RequestParam Optional<String> type) { 3 return recipeService.getRecipesByType(type.orElse(null)); 4}
Here’s what’s happening:
@RequestParam Optional<String> type
: Binds the query parametertype
to anOptional
type, making it clear that the parameter may or may not be present.- The method
getRecipesByType
gets the value from theOptional
ornull
if the parameter is not provided.
Using Optional
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:
Java1@GetMapping("/recipes") 2public List<Recipe> getRecipes(@RequestParam("type") Optional<String> recipeType) { 3 return recipeService.getRecipesByType(recipeType); 4}
Here's what’s happening:
@GetMapping("/recipes")
: Maps HTTP GET requests to/recipes
.@RequestParam("type") Optional<String> recipeType
: 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:
Java1@GetMapping("/category/{recipeCategory}") 2public List<Recipe> getRecipesByCategoryAndDietaryPreference(@PathVariable String recipeCategory, @RequestParam Optional<String> dietaryPreference) { 3 List<Recipe> recipes = recipeRepository.findByCategory(recipeCategory); 4 5 if (dietaryPreference.isPresent()) { 6 return recipes.stream() 7 .filter(recipe -> recipe.getDietaryPreference().equalsIgnoreCase(dietaryPreference.get())) 8 .collect(Collectors.toList()); 9 } 10 11 return recipes; 12}
This example shows how combining path variables and query parameters can create powerful and flexible endpoints. You can now retrieve all recipes within a specific category and further filter them based on dietary preference.
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!