It is common in Spring Boot to perform more than one task in a single controller method. In this short article, you'll learn how to use optional path variables in Spring Boot to handle more than one type of request.

By design in Spring Boot, it is impossible to have optional path variables. Let us say you have the following controller method that handles GET requests on the /todos/{id} endpoint:

@GetMapping(value = {"/todos", "/todos/{id}"})
public @ResponseBody Object fetchTodos(@PathVariable Long id) {
    if (id == null) {
        // return single todo
        return todoRespository.findById(id);
    } else {
        // return all todos
        return todoRespository.findAll();
    }
}

We want to be sure that the above controller method works for both /todos and /todos/1 requests. If the id path variable is present, we just fetch a todo by ID and send it back to the client. Otherwise, simply returns all todos.

Right now, if you skip the id path variable and call the /todos endpoint directly, Spring Boot will throw an exception. One way to avoid this exception is to have two methods, one with the path variable and then another without, as shown below:

// two methods - not recommended
@GetMapping("/todos/{id}")
public @ResponseBody Todo fetchTodoById(@PathVariable Long id) {
    return todoRespository.findById(id);
}

@GetMapping("/todos")
public @ResponseBody List<Todo> fetchAllTodos() {
    return todoRespository.findAll();
}

Technically, the above approach should work fine, but what if you have a bunch of optional path variables? This approach can quickly lead to almost duplicate code blocks.

Using required Attribute

If you are using Spring Boot 2 and higher, the easiest and most efficient way to make any @PathVariable optional is by setting the required attribute to false as shown below:

@GetMapping(value = {"/todos", "/todos/{id}"})
public @ResponseBody Object fetchTodos(@PathVariable(required = false) Long id) {
    if (id == null) {
        return todoRespository.findById(id);
    } else {
        return todoRespository.findAll();
    }
}

That's all you need to do. Now, the above method can handle both types of requests without any exceptions.

Using Java 8 Optional Class

Another way to make a path variable optional in Spring Boot (with Spring 4+) is by using the Java 8 Optional class:

@GetMapping(value = {"/todos", "/todos/{id}"})
public @ResponseBody Object fetchTodos(@PathVariable Optional<Long> id) {
    if (id.isPresent()) {
        return todoRespository.findById(id.get());
    } else {
        return todoRespository.findAll();
    }
}

The Optional class is a special container object which may or may not contain a non-null value. If a value is present, the isPresent() method will return true and get() will return the value.

✌️ Like this article? Follow me on Twitter and LinkedIn. You can also subscribe to RSS Feed.