Implement Update Task

Now that we can create and retrieve tasks, let's implement the ability to update existing tasks. This will allow users to modify task details and mark tasks as complete. We'll need to add methods to both our service and controller layers.

Updating the Service Interface

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

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

Implementing the Service Method

Now let's implement the update 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 Task updateTask(UUID taskListId, UUID taskId, Task task) { if(null == task.getId()) { throw new IllegalArgumentException("Task must have ID!"); } if(!Objects.equals(taskId, task.getId())) { throw new IllegalArgumentException("Task IDs do not match!"); } if(null == task.getPriority()) { throw new IllegalArgumentException("Task must have a valid priority!"); } if(null == task.getStatus()) { throw new IllegalArgumentException("Task must have a valid status!"); } Task existingTask = taskRepository.findByTaskListIdAndId( taskListId, task.getId() ).orElseThrow(() -> new IllegalStateException("Task not found!")); existingTask.setTitle(task.getTitle()); existingTask.setDescription(task.getDescription()); existingTask.setDueDate(task.getDueDate()); existingTask.setPriority(task.getPriority()); existingTask.setStatus(task.getStatus()); existingTask.setUpdated(LocalDateTime.now()); return taskRepository.save(existingTask); } }

Let's examine the key aspects of this implementation:

  1. Input Validation:

    • Checks that the task has an ID
    • Verifies the ID in the path matches the ID in the task
    • Ensures priority and status are set
    • Verifies the task exists in the specified task list
  2. Update Logic:

    • Updates all modifiable fields (title, description, dueDate, priority, status)
    • Updates the 'updated' timestamp
    • Preserves other fields like 'created' timestamp and task list reference
  3. Error Handling:

    • Throws IllegalArgumentException for invalid input
    • Throws IllegalStateException if task doesn't exist
    • These will be caught by our global exception handler

Updating the Controller

Now let's add the update 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; } @PutMapping(path = "/{task_id}") public TaskDto updateTask( @PathVariable("task_list_id") UUID taskListId, @PathVariable("task_id") UUID id, @RequestBody TaskDto taskDto) { Task updatedTask = taskService.updateTask( taskListId, id, taskMapper.fromDto(taskDto) ); return taskMapper.toDto(updatedTask); } }

Let's break down the key components:

  1. URL Mapping:

    • @PutMapping maps this to HTTP PUT requests
    • Path variables capture both task list ID and task ID from the URL
    • Maintains REST resource hierarchy (/task-lists/{task_list_id}/tasks/{task_id})
  2. Parameters:

    • Two @PathVariable annotations extract IDs from the URL
    • @RequestBody deserializes the request body into a TaskDto
  3. Implementation:

    • Converts the incoming DTO to an entity
    • Calls the service to update the task
    • Converts the result back to a DTO for the response

How It Works Together

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

  1. Spring extracts both IDs from the URL path
  2. The request body is deserialized into a TaskDto
  3. The controller converts the DTO to a Task entity
  4. The service:
    • Validates the input
    • Verifies the task exists in the specified task list
    • Updates the task fields
    • Updates the timestamp
    • Saves the changes
  5. The controller converts the updated entity back to a DTO
  6. Spring serializes the DTO to JSON for the response

For example, a PUT request to /task-lists/123/tasks/789 might look like:

{ "id": "789", "title": "Updated Task", "description": "This task has been updated", "dueDate": "2024-11-02T17:00:00", "priority": "HIGH", "status": "CLOSED" }

And receive a response like:

{ "id": "789", "title": "Updated Task", "description": "This task has been updated", "dueDate": "2024-11-02T17:00:00", "priority": "HIGH", "status": "CLOSED" }

Summary

  • Added update functionality to the TaskService
  • Implemented comprehensive validation in the service layer
  • Added the PUT endpoint to update tasks
  • Maintained data integrity by validating IDs and task list ownership
© 2026 Devtiro Ltd. All rights reserved