Dto Mappers

We've implemented the create task method in the service layer, but how do we call it from the REST API?

The first step is to define the REST API's request and response.

Create the Create Task Request DTO

Now we get to see the Data Transfer Object (DTO) pattern in action.

Let's create these presentation-layer classes to represent the create task request and response bodies.

First, let's create a new package for our DTO classes, com.devtiro.task.domain.dto.

In the new package, we'll create the DTO class to represent the request body.

Let's create CreateTaskRequestDto:

/** * A DTO modelling a request to create a new task. * This class is owned by the presentation layer. * * @param title The title of the task to create. * @param description The description of the task to create. * @param dueDate The date and time the task is due. * @param priority The priority of the task. */ public record CreateTaskRequestDto( @NotBlank(message = ERROR_MESSAGE_TITLE_LENGTH) @Length(max = 255, message = ERROR_MESSAGE_TITLE_LENGTH) String title, @Length(max = 1000, message = ERROR_MESSAGE_DESCRIPTION_LENGTH) @Nullable String description, @FutureOrPresent(message = ERROR_MESSAGE_DUE_DATE_FUTURE) @Nullable LocalDate dueDate, @NotNull(message = ERROR_MESSAGE_PRIORITY) TaskPriority priority) { private static final String ERROR_MESSAGE_TITLE_LENGTH = "Title must be between 1 and 255 characters"; private static final String ERROR_MESSAGE_DESCRIPTION_LENGTH = "Description must be less than 1000 characters"; private static final String ERROR_MESSAGE_DUE_DATE_FUTURE = "Due date must be in the future"; private static final String ERROR_MESSAGE_PRIORITY = "Task priority must be provided"; }

We're making good use of validation annotations in the CreateTaskRequestDto class. These annotations ensure the values in these variables are exactly what we expect them to be.

We'll see how to activate these annotations when we implement the task controller. For now, let's move on to the task response DTO.

Create the Create Task Response DTO

Although it may be better practice to create a DTO just for the create task response, we're going to create a generic TaskDto class.

We're going to return task data from a few REST API endpoints. By re-using the same DTO, we have fewer classes to create and manage. Therefore, the build is more approachable, but at the cost of added coupling. A worthwhile trade-off for this build.

Let's create the TaskDto class in com.devtiro.task.domain.dto:

/** * A DTO representing a task. This class is owned by the presentation layer. * * @param id The ID of the task. * @param title The title of the task. * @param description The description of the task. * @param dueDate The date the task is due. * @param priority The priority of the task. * @param status The status of the task. */ public record TaskDto( UUID id, String title, String description, LocalDate dueDate, TaskPriority priority, TaskStatus status) {}

Note that there are no validation annotations on the TaskDto class. This is because the TaskDto class is never a request body, it's always a response body. There's little value in validating a response, so there's no need to add validation annotations to the TaskDto class.

But how do we go from a Task class to a TaskDto?

Mapper

Let's create a dedicated package for our mappers, com.devtiro.task.mapper.

Just like our services, we'll define an interface first and then implement it.

Let's create the TaskMapper interface in our new package:

/** Mapper handling Tasks. */ public interface TaskMapper { CreateTaskRequest fromDto(CreateTaskRequestDto dto); TaskDto toDto(Task task); }

We've defined two methods here, one to map from a CreateTaskRequestDto to a CreateTaskRequest, and the other to map from Task to TaskDto.

Now let's implement the mapper. We'll create an impl package com.devtiro.task.mapper.impl and the class TaskMapperImpl:

@Component public class TaskMapperImpl implements TaskMapper { @Override public CreateTaskRequest fromDto(CreateTaskRequestDto dto) { return new CreateTaskRequest( dto.title(), dto.description(), dto.dueDate(), dto.priority() ); } @Override public TaskDto toDto(Task task) { if (null == task) { return null; } return new TaskDto( task.getId(), task.getTitle(), task.getDescription(), task.getDueDate(), task.getPriority(), task.getStatus()); } }

Note

We can use tools like MapStruct to generate these mappers for us, but we'll avoid them for this build to keep things approachable.

We now have a mapper. Let's use it to implement the task controller class.

Summary

  • Created the CreateTaskRequestDto class.
  • Created the TaskDto class.
  • Defined the TaskMapper interface.
  • Implemented the TaskMapper interface.
© 2026 Devtiro Ltd. All rights reserved