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()
, andtoString()
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
Start the Spring Boot application.
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
Immutability: Records ensure DTOs are immutable, enhancing security and predictability.
Reduced Boilerplate: Records automatically generate essential methods, while MapStruct eliminates manual mapping logic.
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://www.youtube.com/@maheshwarligade
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