Close Menu
    DevStackTipsDevStackTips
    • Home
    • News & Updates
      1. Tech & Work
      2. View All

      Stop writing tests: Automate fully with Generative AI

      August 19, 2025

      Opsera’s Codeglide.ai lets developers easily turn legacy APIs into MCP servers

      August 19, 2025

      Black Duck Security GitHub App, NuGet MCP Server preview, and more – Daily News Digest

      August 19, 2025

      10 Ways Node.js Development Boosts AI & Real-Time Data (2025-2026 Edition)

      August 18, 2025

      This new Coros watch has 3 weeks of battery life and tracks way more – even fly fishing

      August 20, 2025

      5 ways automation can speed up your daily workflow – and implementation is easy

      August 20, 2025

      This new C-suite role is more important than ever in the AI era – here’s why

      August 20, 2025

      iPhone users may finally be able to send encrypted texts to Android friends with iOS 26

      August 20, 2025
    • Development
      1. Algorithms & Data Structures
      2. Artificial Intelligence
      3. Back-End Development
      4. Databases
      5. Front-End Development
      6. Libraries & Frameworks
      7. Machine Learning
      8. Security
      9. Software Engineering
      10. Tools & IDEs
      11. Web Design
      12. Web Development
      13. Web Security
      14. Programming Languages
        • PHP
        • JavaScript
      Featured

      Creating Dynamic Real-Time Features with Laravel Broadcasting

      August 20, 2025
      Recent

      Creating Dynamic Real-Time Features with Laravel Broadcasting

      August 20, 2025

      Understanding Tailwind CSS Safelist: Keep Your Dynamic Classes Safe!

      August 19, 2025

      Sitecore’s Content SDK: Everything You Need to Know

      August 19, 2025
    • Operating Systems
      1. Windows
      2. Linux
      3. macOS
      Featured

      Why GNOME Replaced Eye of GNOME with Loupe as the Default Image Viewer

      August 19, 2025
      Recent

      Why GNOME Replaced Eye of GNOME with Loupe as the Default Image Viewer

      August 19, 2025

      Microsoft admits it broke “Reset this PC” in Windows 11 23H2 KB5063875, Windows 10 KB5063709

      August 19, 2025

      Windows 11 can now screen record specific app windows using Win + Shift + R (Snipping Tool)

      August 19, 2025
    • Learning Resources
      • Books
      • Cheatsheets
      • Tutorials & Guides
    Home»Development»What are Data Transfer Objects? Learn to Use DTOs in Your Java Spring-Based Projects

    What are Data Transfer Objects? Learn to Use DTOs in Your Java Spring-Based Projects

    August 19, 2025

    High performance and privacy are at the heart of most successful software systems. No one wants to use a software service that takes a ridiculous amount of time to load – and no company wants their users’ data exposed at the slightest vulnerability. This is why DTOs are a crucial topic for software engineers to understand.

    Using DTOs is helpful when building applications that hold sensitive data like financial or health records. When used properly, DTOs can prevent sensitive fields from being exposed to the client side. In critical systems, they can further tighten security and reduce failure conditions by ensuring that only valid and required fields are accepted.

    In this article, you’ll learn what DTOs are, why they’re important, and the best ways to create them for your Spring-based applications.

    Prerequisites

    This is a slightly more advanced tutorial. So to understand it better, you should have a sound knowledge of Java concepts like objects, getters and setters, and Spring/Spring Boot. You should also have a solid understanding of how software works in general.

    Table of Contents

    • What is a DTO?

    • How to Create a DTO for a Spring-Based Application

    • How to Create DTOs From Two or Multiple Objects

    • Conclusion

    What is a DTO?

    DTOs stand for Data Transfer Objects. It is a software design pattern that ensures the transfer of tailored/streamlined data objects between different layers of a software system.

    Image showing the lifecycle of DTO in a software system

    Image source | Fabio Ribeiro

    The direction of data transfer with DTOs across the various layers of software is bi-directional. DTOs are either used to carry data in an inbound direction from an external client/user to the software or are constructed and used to carry data in an outbound direction from the software.

    DTOs only hold field data, constructors, and necessary getter and setter methods. So they are Plain Old Java Objects (POJOs).

    You can see the bi-directional flow in the image below:

    Image showing the bi-directional flow of DTO in a software system, flowing from controller to Database and from Database back to the controller

    Image source | Fabio Ribeiro

    Why Use DTOs?

    1. Data Privacy

    In Spring Boot, entities serve as the blueprint for creating data objects. These entities are classes annotated with @Entity and map to a database table. An instance of the entity class represents a database row or record, while a field in the entity class represents a database column.

    When registering for a software service or product, the user might be asked to provide both sensitive and non-sensitive data for the proper functioning of the application. These data are held as fields by the entity class and finally mapped and persisted to the database.

    When we need to retrieve data from the database and expose it through an API endpoint based on the query provided – say, a query to retrieve a user record or entity, Jackson (the serializer dependency commonly used in Spring-based applications) serialises all the data fields contained in the retrieved user entity. Now, imagine you have a User entity that contains fields like password, credit card details, date of birth, home address, and other sensitive data you wouldn’t want to reveal when the User entity is being serialised. Well, this is where DTOs come in.

    With DTOs, you can retrieve the complete entity (containing both sensitive and insensitive data) from the database, create a custom class (say UserDTO.java) that only holds the insensitive fields that you feel are safe to expose, and finally, map the database-retrieved entity to the safe-to-expose UserDTO object. This way, the UserDTO is what gets serialised and exposed through the API endpoint and not the complete entity – keeping the sensitive data confidential.

    2. Improved Software Performance

    DTOs can improve the performance of your software application by reducing the number of API calls for data retrieval. With DTOs, you can return serialized data from more than one entity in just one API call.

    Let’s say that in your Spring Boot application, there are User and Follower entities, and you want to return user data as well as their followers. Typically, Jackson can only serialize one entity at a time, either User or Follower. But with a DTO, you can combine these two entities into one and eventually serialize and return all the data in a single request, instead of building two endpoints to return user and follower data.

    In the next section, I’ll show you the various ways you can create DTOs for your Spring Boot project with code implementations.

    How to Create a DTO for a Spring-Based Application

    There are two main approaches to creating DTOs in Spring/Spring Boot:

    1. Creating Custom Objects and Handling Mapping Manually

    This approach requires you to handle the mapping/transforming of your existing entity to the custom object (DTO) by yourself – that is to say, you write the code that creates the DTO and sets the DTO fields to the values present in the existing entity. This is common for developers who prefer fine-grained control, but it can be tedious for large-scale projects.

    Follow the steps below to create a UserDTO from a User entity:

    Step 1: Create the DTO class

    Create a new file named UserDTO.java and write the code below into it:

    public class UserDTO {
        private Long id;
        private String firstName;
        private String lastName;
        private String email;
    
    
        // No-args Constructor
        public UserDTO() {}
    
        // All-args constructor
        public UserDTO(Long id, String firstName, String lastName, String email) {
            this.id = id;
            this.firstName = firstName;
            this.lastName = lastName;
            this.email = email;
        }
    
    
        // Getters and Setters
        public Long getId() {
            return id;
        }
    
    
        public void setId(Long id) {
            this.id = id;
        }
    
    
        public String getFirstName() {
            return firstName;
        }
    
        public void setFirstName(String firstName) {
            this.firstName = firstName;
        }
    
        public String getLastName() {
            return lastName;
        }
    
    
        public void setLastName(String lastName) {
            this.lastName = lastName;
        }
    
        public String getEmail() {
            return email;
        }
    
        public void setEmail(String email) {
            this.email = email;
        }
    
    }
    

    The defined UserDTO class can only hold four (4) fields: id, firstName, lastName, and email. It’s not capable of exposing or receiving more than this number of fields. The class also contains getter and setter methods for retrieving and assigning data to the fields.

    Step 2: Create Mapper Methods Inside a Utility Class

    Create a new file named UserMapper.java and put this piece of code into it:

    public class UserMapper {
    
        // Convert Entity to DTO
    
        public static UserDTO toDTO(UserEntity user) {
    
            if (user == null) return null;
    
            UserDTO dto = new UserDTO();
            dto.setId(user.getId());
            dto.setFirstName(user.getFirstName());
            dto.setLastName(user.getLastName());
            dto.setEmail(user.getEmail());
            return dto;
        }
    
    
        // Convert DTO to Entity
    
        public static UserEntity toEntity(UserDTO dto) {
    
            if (dto == null) return null;
           UserEntity user = new UserEntity(); 
            user.setFirstName(dto.getFirstName());
            user.setLastName(dto.getLastName());
            user.setEmail(dto.getEmail());
    
            return user;
        }
    

    The UserMapper class is a utility class that maps the UserEntity to a DTO and the DTO to an entity. This is where the bi-directional data transfer I talked about earlier comes into play. First, the UserEntity-DTO-direction involves retrieving the complete record from the database and transforming it into a streamlined object (void of unnecessary information) before it’s serialized and exposed to the client side through an API endpoint.

    The DTO-UserEntity-direction involves taking the object from the client side as input into the system, but this time, to limit the client in terms of the number of data fields they can pass to the system. This object is received, mapped to an entity, and saved in the system. This is important when you don’t want the client to have access to certain critical fields (that would make your application vulnerable). That’s why software engineers always say, “Don’t trust the user”.

    Let me give you a peek into what our UserEntity looks like:

    import jakarta.persistence.*;
    
    import java.time.LocalDate;
    
    
    @Entity
    
    @Table(name = "users")
    
    public class UserEntity {
    
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
    
        private Long id;
        private String firstName;
        private String lastName;
    
        @Column(unique = true)
        private String email;
        private String password;
        private String phoneNumber;
        private String gender;
        private LocalDate dateOfBirth;
        private String address;
        private String city;
        private String state;
        private String country;
        private String profilePictureUrl;
        private boolean isVerified;
        private LocalDate createdAt;
        private LocalDate updatedAt;
    
        // Constructors
        public UserEntity() {}
    
        // Getters and Setters
        public Long getId() {
            return id;
        }
    
        public void setId(Long id) {
            this.id = id;
        }
    
        public String getFirstName() {
            return firstName;
        }
    
        public void setFirstName(String firstName) {
            this.firstName = firstName;
        }
    
        public String getLastName() {
            return lastName;
        }
    
        public void setLastName(String lastName) {
            this.lastName = lastName;
        }
    
        public String getEmail() {
            return email;
        }
    
        public void setEmail(String email) {
            this.email = email;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    
        public String getPhoneNumber() {
            return phoneNumber;
        }
    
        public void setPhoneNumber(String phoneNumber) {
            this.phoneNumber = phoneNumber;
        }
    
        public String getGender() {
            return gender;
        }
    
        public void setGender(String gender) {
            this.gender = gender;
        }
    
        public LocalDate getDateOfBirth() {
            return dateOfBirth;
        }
    
        public void setDateOfBirth(LocalDate dateOfBirth) {
            this.dateOfBirth = dateOfBirth;
        }
    
        public String getAddress() {
            return address;
        }
    
        public void setAddress(String address) {
            this.address = address;
        }
    
        public String getCity() {
            return city;
        }
    
        public void setCity(String city) {
            this.city = city;
        }
    
        public String getState() {
            return state;
        }
    
        public void setState(String state) {
            this.state = state;
        }
    
        public String getCountry() {
           return country;
        }
    
        public void setCountry(String country) {
            this.country = country;
        }
    
        public String getProfilePictureUrl() {
            return profilePictureUrl;
        }
    
        public void setProfilePictureUrl(String profilePictureUrl) {
            this.profilePictureUrl = profilePictureUrl;
        }
    
        public boolean isVerified() {
            return isVerified;
        }
    
        public void setVerified(boolean verified) {
            isVerified = verified;
        }
    
        public LocalDate getCreatedAt() {
            return createdAt;
        }
    
        public void setCreatedAt(LocalDate createdAt) {
            this.createdAt = createdAt;
        }
    
        public LocalDate getUpdatedAt() {
            return updatedAt;
        }
    
        public void setUpdatedAt(LocalDate updatedAt) {
            this.updatedAt = updatedAt;
        }
    
    }
    

    In the code snippet above, you can see that the UserDTO holds just four (4) fields, which are insensitive and safe to expose upon serialization. These fields are id, firstName, lastName, and email – unlike the UserEntity, which contains both the sensitive and insensitive fields. So, the not-safe-to-expose UserEntity maps to the safe-to-expose UserDTO. With that being done, the UserDTO object can be serialized and returned through an endpoint. You can now see why DTOs help us prevent exposing confidential information.

    2. Creating Custom Objects and Handling Mapping Through an External Library

    Using an external library means adding a layer of abstraction to the mapping process. The library handles the stressful parts of the job for you, and it’s often a preferred choice for large-scale projects. In this article, we’re using MapStruct because it’s popular and easy to use. Maven will be our build tool.

    Step 1: Add the dependency to your project

    Since you are using Maven as your build tool, open your pom.xml file and add this code:

    <dependencies>
        <!-- MapStruct API -->
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct</artifactId>
            <version>1.5.5.Final</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <!-- Annotation processor plugin -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.10.1</version>
                <configuration>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.mapstruct</groupId>
                            <artifactId>mapstruct-processor</artifactId>
                            <version>1.5.5.Final</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
        </plugins>
    </build>
    

    This will help download the dependency during the project build.

    Step 2: Define your DTO

    Use the UserDTO.java file given in step 1 of the first approach.

    Step 3: Create the MapStruct Mapper Interface

    Create a file and name it UserMapper.java, and add the code below to it:

    import org.mapstruct.Mapper;
    import org.mapstruct.factory.Mappers;
    
    @Mapper(componentModel = "spring")
    public interface UserMapper {
        UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
        UserDTO toDTO(UserEntity user);
        UserEntity toEntity(UserDTO userDTO);
    }
    

    The UserMapper interface contains the INSTANCE field and two methods, namely toDTO and toEntity, that take in objects of types UserEntity and UserDTO, respectively, as arguments. The implementations of these methods are abstracted and handled by the library for us.

    You can now use the mapper methods (toDTO and toEntity) in your Service or Controller.

    How to Create DTOs From Two or Multiple Objects

    This is one of the most important ways to use DTOs: creating DTOs from more than one entity and combining them as one, so that they can be returned in one API call or request.

    There are many ways you can apply this technique and create complex response DTOs, based on the requirements of your project. The form or structure of your API response object might not be the same as the example given in this tutorial – but the same principle applies, which is simply creating individual DTOs and combining them into one DTO, which eventually serves as the response DTO.

    The example below isn’t super complex, but it’s sufficient to help you understand how this works so you can leverage the technique in creating more complex API response objects. This example will combine DTOs of a doctor and their appointments.

    Step 1: Create the DTO Classes

    Create a file named DoctorDto.java and add this code to it:

    
    public class DoctorProfileDTO {
        private String doctorId;
        private String fullName;
        private String email;
        private String specialization;
    
       // No-args constructor
       public DoctorProfileDTO(){
        }
    
        // Getter and Setter for doctorId
        public String getDoctorId() {
            return doctorId;
        }
    
        public void setDoctorId(String doctorId) {
            this.doctorId = doctorId;
        }
    
        // Getter and Setter for fullName
        public String getFullName() {
            return fullName;
        }
    
        public void setFullName(String fullName) {
            this.fullName = fullName;
        }
    
        // Getter and Setter for email
        public String getEmail() {
            return email;
        }
    
        public void setEmail(String email) {
            this.email = email;
        }
    
        // Getter and Setter for specialization
        public String getSpecialization() {
            return specialization;
        }
    
        public void setSpecialization(String specialization) {
            this.specialization = specialization;
        }
    }
    

    Create another one called AppointmentDto.java and include this code:

    public class AppointmentDTO {
        private String appointmentId;
        private String appointmentDate;
        private String status;
        private String patientName;
        private String patientEmail;
    
       // No-args constructor
       public AppointmentDTO(){
        }
    
        // Getter and Setter for appointmentId
        public String getAppointmentId() {
            return appointmentId;
        }
    
        public void setAppointmentId(String appointmentId) {
            this.appointmentId = appointmentId;
        }
    
        // Getter and Setter for appointmentDate
        public String getAppointmentDate() {
            return appointmentDate;
        }
    
        public void setAppointmentDate(String appointmentDate) {
            this.appointmentDate = appointmentDate;
        }
    
        // Getter and Setter for status
        public String getStatus() {
            return status;
        }
    
        public void setStatus(String status) {
            this.status = status;
        }
    
        // Getter and Setter for patientName
        public String getPatientName() {
            return patientName;
        }
    
        public void setPatientName(String patientName) {
            this.patientName = patientName;
        }
    
        // Getter and Setter for patientEmail
        public String getPatientEmail() {
            return patientEmail;
        }
    
        public void setPatientEmail(String patientEmail) {
            this.patientEmail = patientEmail;
        }
    }
    

    Step 2: Create a Composite DTO Combining both Entities

    Create a file named DoctorWithAppointmentsDTO.java:

    import java.util.List;
    
    public class DoctorWithAppointmentsDTO {
        private DoctorProfileDTO doctorProfile;
        private List<AppointmentDTO> appointments;
    
        // No-args constructor
        public DoctorWithAppointmentsDTO() {
        }
    
        // Getter and Setter for doctorProfile
        public DoctorProfileDTO getDoctorProfile() {
            return doctorProfile;
        }
    
        public void setDoctorProfile(DoctorProfileDTO doctorProfile) {
            this.doctorProfile = doctorProfile;
        }
    
        // Getter and Setter for appointments
        public List<AppointmentDTO> getAppointments() {
            return appointments;
        }
    
        public void setAppointments(List<AppointmentDTO> appointments) {
            this.appointments = appointments;
        }
    }
    

    Step 3: Create a Mapper Class

    Create a mapper class DoctorMapper.java containing the logic to map to the DoctorWithAppointmentsDTO class:

    public class MapperClass {
    
        public DoctorWithAppointmentsDTO toDTO(Doctor doctor, List<Appointment> appointments) {
    
            DoctorProfileDTO doctorProfile = new DoctorProfileDTO();
            doctorProfile.setDoctorId(doctor.getId());
            doctorProfile.setFullName(doctor.getFullName());
            doctorProfile.setEmail(doctor.getEmail());
            doctorProfile.setSpecialization(doctor.getSpecialization());
    
            List<AppointmentDTO> appointmentDTOs = appointments.stream().map(appt -> {
                AppointmentDTO dto = new AppointmentDTO();
                dto.setAppointmentId(appt.getId());
                dto.setAppointmentDate(appt.getDate().toString()); 
                dto.setStatus(appt.getStatus().name());
                dto.setPatientName(appt.getPatient().getName());
                dto.setPatientEmail(appt.getPatient().getEmail());
                return dto;
            }).toList();
    
            DoctorWithAppointmentsDTO doctorWithAppointment = new DoctorWithAppointmentsDTO();
            doctorWithAppointment.setDoctorProfile(doctorProfile);
            doctorWithAppointment.setAppointments(appointmentDTOs);
    
            return doctorWithAppointment;
        }
    }
    

    From the example above, you can see that two separate DTOs (AppointmentDTO and DoctorProfileDTO) were created before the composite DTO, DoctorWithAppointmentsDTO was created. The composite DTO class (DoctorWithAppointmentsDTO) contains fields that hold the instances of the Appointment and DoctorProfile DTOs. The mapper class takes in the Doctor and a list of Appointment entities as arguments, maps them to DoctorProfileDTO and AppointmentDTO, respectively. Finally, the fields for the composite DTO class are set using the DTO objects mapped from the entities.

    The DoctorWithAppointmentsDTO, when serialised and returned through an endpoint, should give you an output like this:

    {
      "doctorProfile": {
        "doctorId": "abc123",
        "fullName": "Dr. Susan Emeka",
        "email": "suzan.emeka@example.com",
        "specialisation": "Cardiology"
      },
      "appointments": [
        {
          "appointmentId": "appt001",
          "appointmentDate": "2025-07-10T09:00:00",
          "status": "CONFIRMED",
          "patientName": "James Agaji",
          "patientEmail": "james.agaji@example.com"
        },
     {
          "appointmentId": "appt002",
          "appointmentDate": "2025-08-12T07:05:08",
          "status": "CONFIRMED",
          "patientName": "Jane Augustine",
          "patientEmail": "jane.augustine@example.com"
        }
      ]
    }
    

    Conclusion

    If you’re a software engineer who’s concerned with privacy and efficiency, using DTOs in your applications is a must.

    In this article, you’ve learned what DTOs are as well as the main approaches for creating and using them. Take the time to go through the code snippets given in this article and practice with them until you’re comfortable implementing them yourself. Thanks for reading.

    Source: freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More 

    Facebook Twitter Reddit Email Copy Link
    Previous ArticleUnderstanding Tailwind CSS Safelist: Keep Your Dynamic Classes Safe!
    Next Article How to Convert Your Website into an Android App Using Bubblewrap

    Related Posts

    Development

    Creating Dynamic Real-Time Features with Laravel Broadcasting

    August 20, 2025
    Repurposing Protein Folding Models for Generation with Latent Diffusion
    Artificial Intelligence

    Repurposing Protein Folding Models for Generation with Latent Diffusion

    August 20, 2025
    Leave A Reply Cancel Reply

    For security, use of Google's reCAPTCHA service is required which is subject to the Google Privacy Policy and Terms of Use.

    Continue Reading

    Public Exploit for Chained SAP Flaws Exposes Unpatched Systems to Remote Code Execution

    Development

    Case Studies: Real-World Applications of Context Engineering

    Machine Learning

    InstallAware Launches Setup Virtualization Technology “Project OS/3,” Running Windows Binaries Directly on Linux and macOS

    Tech & Work

    Pen Testing for Compliance Only? It’s Time to Change Your Approach

    Development

    Highlights

    Linux

    FOSS Weekly #25.14: Fedora 42 COSMIC, OnePackage, AppImage Tools and More Linux Stuff

    April 3, 2025

    Linux distributions agreeing to a single universal packaging system? That sounds like a joke, right?…

    Rilasciato digiKam 8.7: Nuovo Strumento e Miglioramenti per la Gestione Fotografica

    July 1, 2025

    SWE-Bench Performance Reaches 50.8% Without Tool Use: A Case for Monolithic State-in-Context Agents

    May 18, 2025

    Code Implementation to Building a Model Context Protocol (MCP) Server and Connecting It with Claude Desktop

    April 13, 2025
    © DevStackTips 2025. All rights reserved.
    • Contact
    • Privacy Policy

    Type above and press Enter to search. Press Esc to cancel.