Implement Get Task

Now that we can list and create tasks, let's implement the ability to retrieve a single task by its ID. This functionality is essential for viewing and editing individual tasks. We'll need to add methods to both our service and controller layers.

Updating the Service Interface

First, let's add the get method to our TaskService interface:

public interface TaskService { List<Task> listTasks(UUID taskListId); Task createTask(UUID taskListId, Task task); Optional<Task> getTask(UUID taskListId, UUID taskId); // Add this method }

We're returning an Optional<Task> because the requested task might not exist.

Implementing the Service Method

Now let's implement the get functionality in TaskServiceImpl:

@Service public class TaskServiceImpl implements TaskService { private final TaskListRepository taskListRepository; private final TaskRepository taskRepository; public TaskServiceImpl(TaskListRepository taskListRepository, TaskRepository taskRepository) { this.taskListRepository = taskListRepository; this.taskRepository = taskRepository; } @Override public Optional<Task> getTask(UUID taskListId, UUID taskId) { return taskRepository.findByTaskListIdAndId(taskListId, taskId); } }

This implementation:

  1. Takes two parameters:
    • taskListId to scope the search to a specific task list
    • taskId to identify the specific task
  2. Delegates to our custom repository method findByTaskListIdAndId
  3. Returns an Optional<Task> which will be empty if no matching task exists

Updating the Controller

Now let's add the get endpoint to our TasksController:

@RestController @RequestMapping(path = "/task-lists/{task_list_id}/tasks") public class TasksController { private final TaskService taskService; private final TaskMapper taskMapper; public TasksController(TaskService taskService, TaskMapper taskMapper) { this.taskService = taskService; this.taskMapper = taskMapper; } @GetMapping("/{task_id}") public Optional<TaskDto> getTask( @PathVariable("task_list_id") UUID taskListId, @PathVariable("task_id") UUID taskId) { return taskService.getTask(taskListId, taskId) .map(taskMapper::toDto); } }

Let's break down the key components:

  1. URL Path:

    • @GetMapping("/{task_id}") maps this to GET requests with a task ID in the path
    • The path includes both task list ID and task ID
    • Follows REST resource nesting pattern (/task-lists/{id}/tasks/{id})
  2. Path Variables:

    • Two @PathVariable annotations extract both IDs from the URL
    • Spring automatically converts the string IDs to UUIDs
  3. Implementation:

    • Calls the service with both IDs to get the task
    • Uses map to transform the Task to TaskDto if present
    • Returns an empty Optional if no task is found

How It Works Together

When a GET request arrives at /task-lists/{task_list_id}/tasks/{task_id}:

  1. Spring extracts both IDs from the URL path
  2. The controller calls taskService.getTask()
  3. The service delegates to taskRepository.findByTaskListIdAndId()
  4. If found:
    • The repository returns the Task entity
    • The controller converts it to a DTO
    • Spring serializes the DTO to JSON
  5. If not found:
    • An empty Optional is returned
    • Spring converts this to a 200 OK with null body

For example, a GET request to /task-lists/123/tasks/789 might return:

{ "id": "789", "title": "Complete Documentation", "description": "Write API documentation for the task endpoints", "dueDate": "2024-11-01T09:00:00", "priority": "HIGH", "status": "OPEN" }

Summary

  • Added get functionality to the TaskService
  • Implemented the get endpoint in the controller
  • Used Optional to handle cases where the task isn't found
  • Added proper task list scoping to ensure tasks are only accessed in their correct context
© 2026 Devtiro Ltd. All rights reserved