Effortlessly Create DTOs with Java Records and MapStruct in Spring Boot!

In modern Spring Boot applications, Data Transfer Objects (DTOs) play a vital role in ensuring clean code, maintaining separation of concerns, and improving application security. With the advent of Java Records, creating immutable DTOs has become simpler than ever. When paired with MapStruct, an efficient mapping framework, handling object conversions becomes a breeze.

In this article, you’ll learn:

  • What Java Records are and why they’re great for DTOs.

  • How to use MapStruct to map entities to DTOs and vice versa.

  • A complete example showcasing DTOs with Records and MapStruct in Spring Boot.


What Are Java Records?

Java Records, introduced in Java 14 (as a preview) and officially in Java 16, are a concise way to declare classes that are primarily used to store data. They are:

  • Immutable: Fields of records cannot be changed after the object is created.

  • Boilerplate-Free: Automatically generates constructors, getters, equals(), hashCode(), and toString() methods.

Example of a Record:

public record CustomerDTO(Long id, String name, String email) {}

Why use Records for DTOs?

  • Immutability ensures data integrity.

  • Compact Syntax reduces clutter, focusing on the data.


What Is MapStruct?

MapStruct is a Java-based code generator that simplifies mapping between objects. Instead of manually writing boilerplate mapping logic, MapStruct generates efficient and type-safe mappers at compile time.


Setting Up the Project

Add the required dependencies to your pom.xml:

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.5.3.Final</version>
</dependency>
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
    <version>1.5.3.Final</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

Example: Using Records and MapStruct for DTOs

1. Define the Entity

Here’s the Customer entity:

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

@Entity
public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private String email;

    // Getters and Setters
}

2. Create a DTO Using Java Records

Define a CustomerDTO:

public record CustomerDTO(Long id, String name, String email) {}

3. Create a MapStruct Mapper

Define an interface for mapping:

import org.mapstruct.Mapper;
import org.mapstruct.Mapping;

@Mapper(componentModel = "spring")
public interface CustomerMapper {
    CustomerDTO toDTO(Customer customer);
    Customer toEntity(CustomerDTO customerDTO);
}

Key Points:

  • The @Mapper annotation marks this as a MapStruct mapper.

  • The componentModel = "spring" ensures the mapper is a Spring Bean.


4. Service Layer

Use the mapper in the service layer:

import org.springframework.stereotype.Service;

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

@Service
public class CustomerService {
    private final CustomerRepository customerRepository;
    private final CustomerMapper customerMapper;

    public CustomerService(CustomerRepository customerRepository, CustomerMapper customerMapper) {
        this.customerRepository = customerRepository;
        this.customerMapper = customerMapper;
    }

    public List<CustomerDTO> getAllCustomers() {
        return customerRepository.findAll().stream()
            .map(customerMapper::toDTO)
            .collect(Collectors.toList());
    }

    public CustomerDTO saveCustomer(CustomerDTO customerDTO) {
        Customer customer = customerMapper.toEntity(customerDTO);
        Customer savedCustomer = customerRepository.save(customer);
        return customerMapper.toDTO(savedCustomer);
    }
}

5. Expose an API Endpoint

Create a controller to expose the API:

import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/customers")
public class CustomerController {

    private final CustomerService customerService;

    public CustomerController(CustomerService customerService) {
        this.customerService = customerService;
    }

    @GetMapping
    public List<CustomerDTO> getAllCustomers() {
        return customerService.getAllCustomers();
    }

    @PostMapping
    public CustomerDTO saveCustomer(@RequestBody CustomerDTO customerDTO) {
        return customerService.saveCustomer(customerDTO);
    }
}

Testing the Application

  1. Start the Spring Boot application.

  2. Use Postman or curl to test the endpoints:

    • Get all customers:

        GET /customers
      
    • Save a new customer:

        POST /customers
        {
            "name": "Alice Johnson",
            "email": "alice@example.com"
        }
      

Advantages of Using Records and MapStruct

  1. Immutability: Records ensure DTOs are immutable, enhancing security and predictability.

  2. Reduced Boilerplate: Records automatically generate essential methods, while MapStruct eliminates manual mapping logic.

  3. Type-Safe Mappings: MapStruct enforces type safety during compile time, reducing runtime errors.


Conclusion

Combining Java Records and MapStruct is a game-changer for building clean, efficient, and maintainable Spring Boot applications. With immutable DTOs and automated object mapping, you can simplify your codebase and focus more on solving business problems.

Start integrating these tools in your projects today, and experience the difference they bring to your development workflow!

More such articles:

https://medium.com/techwasti

https://www.youtube.com/@maheshwarligade

https://techwasti.com/series/spring-boot-tutorials

https://techwasti.com/series/go-language

0
Subscribe to my newsletter

Read articles from Maheshwar Ligade directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Maheshwar Ligade
Maheshwar Ligade

Learner, Love to make things simple, Full Stack Developer, StackOverflower, Passionate about using machine learning, deep learning and AI