Using DTO Pattern in Spring Boot

Introduction

In this tutorial, we will learn how to use the DTO (Data Transfer Object) pattern in a Spring Boot application. Martin Fowler describes the Data Transfer Object in his famous book Patterns of Enterprise Application Architecture. The main idea of DTOs is to reduce the number of remote calls that are expensive.

Data Transfer Object Design Pattern is a frequently used design pattern. It is basically used to pass data with multiple attributes in one shot from client to server to avoid multiple calls to a remote server.

Benefits of Using DTO Pattern in Spring Boot

Loose Coupling: Using DTOs decouples the API layer from the persistence layer. This prevents direct exposure of entity classes and promotes a more modular and flexible architecture.

Improved Security: DTOs help in preventing the overexposure of sensitive data by including only the necessary fields required by the client. This minimizes the risk of exposing sensitive information like passwords or internal IDs.

Better Performance: By transferring only the required data, DTOs reduce the amount of data being sent over the network. This leads to better performance, especially in distributed systems where network latency and bandwidth are critical factors.

Simplified API Design: DTOs provide a clear and concise API design by exposing only the necessary data fields. This makes the API easier for clients to understand and use.

Validation and Transformation: DTOs can be used to validate and transform data before it reaches the business logic layer. This ensures that the data is in the correct format and meets the required constraints before being processed.

Compatibility with Different Clients: DTOs can be customized to fit the needs of different clients, such as web browsers, mobile apps, or other services. This allows the API to serve different clients with tailored data representations without changing the underlying business logic.

Let’s implement the Spring Boot DTO example step by step.

Technologies Used

  • Java 21
  • Spring Boot
  • Spring Data JPA (Hibernate)
  • H2 in-memory database
  • Maven
  • Lombok
  • Eclipse STS IDE

1. Create a Spring Boot Project

We’ll use Spring Initializr to bootstrap our application.

  • Go to Spring Initializr
  • Select Java in the language section.
  • Enter Artifact as spring-dto-tutorial
  • Add Spring Web, Lombok, Spring Data JPA, and H2 dependencies.
  • Click Generate to generate and download the project.
  • Once the project is generated, unzip it and import it into your favorite IDE.

2. Maven Dependencies

Open the pom.xml file and confirm the following dependencies are present:

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.modelmapper</groupId>
            <artifactId>modelmapper</artifactId>
            <version>2.4.4</version>
        </dependency>
    </dependencies>

3. Model Layer

Let’s create User and Location JPA entities and establish a many-to-one relationship between them.

User JPA Entity

package net.javaguides.springboot.model;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import jakarta.persistence.*;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    private String email;

    @Column(name = "first_name")
    private String firstName;

    @Column(name = "last_name")
    private String lastName;

    private String password;

    @ManyToOne(fetch = FetchType.EAGER, optional = false)
    @JoinColumn(name = "location_id")
    private Location location;
}

Explanation

  • @Entity and @Table: Indicate that this class is a JPA entity and maps to the users table.
  • @Id and @GeneratedValue: Specify the primary key and its generation strategy.
  • @Column: Customizes the mapping between the field and the column.
  • @ManyToOne and @JoinColumn: Define the many-to-one relationship with the Location entity.

Location JPA Entity

package net.javaguides.springboot.model;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import jakarta.persistence.*;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "locations")
public class Location {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    private String place;
    private String description;
    private double longitude;
    private double latitude;
}

Explanation

  • @Entity and @Table: Indicate that this class is a JPA entity and maps to the locations table.
  • @Id and @GeneratedValue: Specify the primary key and its generation strategy.

4. Repository Layer

Let’s create Spring Data JPA repositories to interact with the database.

UserRepository

package net.javaguides.springboot.repository;

import net.javaguides.springboot.model.User;
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Long> {
}

Explanation

  • JpaRepository: Provides CRUD operations for the User entity.

LocationRepository

package net.javaguides.springboot.repository;

import net.javaguides.springboot.model.Location;
import org.springframework.data.jpa.repository.JpaRepository;

public interface LocationRepository extends JpaRepository<Location, Long> {
}

Explanation

  • JpaRepository: Provides CRUD operations for the Location entity.

5. DTO Layer

Let’s create a DTO to transfer data between the server and the client.

UserLocationDTO

package net.javaguides.springboot.dto;

import lombok.Data;

@Data
public class UserLocationDTO {
    private long userId;
    private String email;
    private String place;
    private double longitude;
    private double latitude;
}

Explanation

  • @Data: Lombok annotation that generates getters, setters, and other utility methods.

6. Service Layer

Let’s create a service class and keep the entity-to-DTO conversion logic in the service class.

UserService

package net.javaguides.springboot.service;

import net.javaguides.springboot.dto.UserLocationDTO;
import net.javaguides.springboot.model.User;
import net.javaguides.springboot.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.stream.Collectors;

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    public List<UserLocationDTO> getAllUsersLocation() {
        return userRepository.findAll()
                .stream()
                .map(this::convertEntityToDto)
                .collect(Collectors.toList());
    }

    private UserLocationDTO convertEntityToDto(User user) {
        UserLocationDTO userLocationDTO = new UserLocationDTO();
        userLocationDTO.setUserId(user.getId());
        userLocationDTO.setEmail(user.getEmail());
        userLocationDTO.setPlace(user.getLocation().getPlace());
        userLocationDTO.setLongitude(user.getLocation().getLongitude());
        userLocationDTO.setLatitude(user.getLocation().getLatitude());
        return userLocationDTO;
    }
}

Explanation

  • @Service: Marks the class as a service provider.
  • convertEntityToDto: Converts a User entity to a UserLocationDTO.

7. Controller Layer

Let’s create a controller and expose a simple REST API that returns a list of DTO objects.

UserController

package net.javaguides.springboot.controller;

import net.javaguides.springboot.dto.UserLocationDTO;
import net.javaguides.springboot.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/users-location")
    public List<UserLocationDTO> getAllUsers

Location() {
        return userService.getAllUsersLocation();
    }
}

Explanation

  • @RestController: Indicates that the class is a REST controller.
  • @GetMapping("/users-location"): Maps the /users-location endpoint to the getAllUsersLocation method.

8. Insert Data into DB

Let’s write a code to insert a few records into the H2 database.

SpringbootDtoTutorialApplication

package net.javaguides.springboot;

import net.javaguides.springboot.model.Location;
import net.javaguides.springboot.model.User;
import net.javaguides.springboot.repository.LocationRepository;
import net.javaguides.springboot.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringbootDtoTutorialApplication implements CommandLineRunner {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootDtoTutorialApplication.class, args);
    }

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private LocationRepository locationRepository;

    @Override
    public void run(String... args) throws Exception {
        Location location = new Location();
        location.setPlace("Pune");
        location.setDescription("Pune is a great place to live");
        location.setLongitude(40.5);
        location.setLatitude(30.6);
        locationRepository.save(location);

        User user1 = new User();
        user1.setFirstName("Ramesh");
        user1.setLastName("Fadatare");
        user1.setEmail("ramesh@gmail.com");
        user1.setPassword("secret");
        user1.setLocation(location);
        userRepository.save(user1);

        User user2 = new User();
        user2.setFirstName("John");
        user2.setLastName("Cena");
        user2.setEmail("john@gmail.com");
        user2.setPassword("secret");
        user2.setLocation(location);
        userRepository.save(user2);
    }
}

Explanation

  • @SpringBootApplication: Marks the class as a Spring Boot application.
  • CommandLineRunner: Interface that indicates a run method should be invoked when the application starts.

9. Run and Test the Spring Boot Application

Go to the root directory of the application and type the following command to run it:

$ mvn spring-boot:run

The application will start at Spring Boot’s default Tomcat port, 8080.

Hit the below URL in the browser to get the response of the REST API:

http://localhost:8080/users-location

Conclusion

In this tutorial, we have seen how to use the DTO (Data Transfer Object) pattern in a Spring Boot application. DTOs allow us to transfer just the required information, reducing coupling between the API and the persistence model and making the service easier to maintain and scale.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top