Lesson 3
Handling Path Variables and Query Parameters in Spring Boot
Introduction

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!

Understanding Path Variables

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, the recipeId variable will take the value 123.
  • When someone requests /recipes/pizza-pepperoni, the recipeId variable will take the value pizza-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.

Path Variables Example

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:

Kotlin
1@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:

  1. @GetMapping("/recipes/{recipeId}): Maps HTTP GET requests to /recipes/{recipeId}. {recipeId} is a path variable.
  2. @PathVariable recipeId: Long: Binds the path variable recipeId from the URL to the method parameter.
  3. The method interacts with recipeRepository to find a recipe by its ID.
  4. If no matching recipe is found, it throws an IllegalArgumentException.

Path variables make your URLs more dynamic and informative.

Named Path Variables

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:

Kotlin
1@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:

  1. @GetMapping("/recipes/{recipeId}): Maps HTTP GET requests to /recipes/{recipeId}.
  2. @PathVariable("recipeId") id: Long: Binds the path variable recipeId from the URL to the method parameter id.
  3. The rest works as before.

This approach provides clarity and flexibility when the names differ.

Understanding Query Parameters

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.
Query Parameters Example

Suppose you want to filter recipes by type (e.g., vegan, vegetarian). Here’s how to implement this:

Kotlin
1@GetMapping("/recipes") 2fun getRecipesByType(@RequestParam type: String): List<Recipe> { 3 return recipeService.getRecipesByType(type) 4}

Here's what’s happening:

  1. @GetMapping("/recipes"): Maps HTTP GET requests to /recipes.
  2. @RequestParam type: String: Binds the query parameter type to the method parameter.
  3. The method calls getRecipesByType in recipeService to get recipes filtered by type.

Query parameters enable more detailed and specific requests to your API.

Making Query Parameters Optional

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:

Kotlin
1@GetMapping("/recipes") 2fun getRecipesByType(@RequestParam type: String?): List<Recipe> { 3 return recipeService.getRecipesByType(type) 4}

Here’s what’s happening:

  1. @RequestParam type: String?: Binds the query parameter type to a nullable type, making it clear that the parameter may or may not be present.
  2. The method getRecipesByType will handle the case where type is null appropriately.

Using nullable types in Kotlin provides a clean and explicit way to handle optional query parameters, enhancing code readability and maintainability.

Named Query Parameters

When the query parameter name differs from the method parameter name, you can explicitly name the query parameter. Here’s how:

Kotlin
1@GetMapping("/recipes") 2fun getRecipes(@RequestParam("type") recipeType: String?): List<Recipe> { 3 return recipeService.getRecipesByType(recipeType) 4}

Here's what’s happening:

  1. @GetMapping("/recipes"): Maps HTTP GET requests to /recipes.
  2. @RequestParam("type") recipeType: String?: Binds the query parameter type from the URL to the method parameter recipeType.

This approach provides flexibility when names differ.

Combining Path Variables and Query Parameters

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:

Kotlin
1@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:

  1. @GetMapping("/category/{recipeCategory}"): Maps HTTP GET requests to /category/{recipeCategory}.
  2. @PathVariable recipeCategory: String: Binds the path variable recipeCategory from the URL to the method parameter.
  3. @RequestParam dietaryPreference: String?: Binds the query parameter dietaryPreference, which is optional, to the method parameter.
  4. The method retrieves recipes by category from recipeRepository.
  5. If dietaryPreference is provided, it filters the recipes by matching the dietary preference. If not, it returns all recipes within the specified category.
Summary

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!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.